From ee89fe6a33b5f92824f7b664a902a03ab4e80a19 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Tue, 16 Jan 2024 17:40:22 +0100 Subject: [PATCH 01/51] wrap_FreeGroup --- src/sage/groups/free_group.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 5bc575ae264..732bc10a546 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -729,7 +729,8 @@ def wrap_FreeGroup(libgap_free_group): assert libgap_free_group.IsFreeGroup() libgap_free_group._set_compare_by_id() names = tuple( str(g) for g in libgap_free_group.GeneratorsOfGroup() ) - return FreeGroup_class(names, libgap_free_group) + # return FreeGroup_class(names, libgap_free_group) + return FreeGroup(names) class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): From 5a4dba0b23fd3a3bbd7cb223a6dd30ca678ebcf6 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sat, 20 Jan 2024 09:52:16 +0100 Subject: [PATCH 02/51] wrap_FpGroup --- src/sage/groups/finitely_presented.py | 4 +++- src/sage/groups/free_group.py | 11 +++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 004f1445685..9847b241371 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -411,9 +411,11 @@ def wrap_FpGroup(libgap_fpgroup): libgap_fpgroup._set_compare_by_id() from sage.groups.free_group import wrap_FreeGroup free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup()) + names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) relations = tuple(free_group(rel.UnderlyingElement()) for rel in libgap_fpgroup.RelatorsOfFpGroup()) - return FinitelyPresentedGroup(free_group, relations) + relations_Tietze = tuple(rel.Tietze() for rel in relations) + return FreeGroup(names) / relations_Tietze class RewritingSystem(): diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 732bc10a546..6f3c4400779 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -148,7 +148,7 @@ def _lexi_gen(zeroes=False): """ count = Integer(0) while True: - mwrap, ind = count.quo_rem(26) + mwrap, ind = count.quo_rem(26) if mwrap == 0 and not zeroes: name = '' else: @@ -505,12 +505,12 @@ def syllables(self): """ g = self.gap().UnderlyingElement() k = g.NumberSyllables().sage() - exponent_syllable = libgap.eval('ExponentSyllable') + exponent_syllable = libgap.eval('ExponentSyllable') generator_syllable = libgap.eval('GeneratorSyllable') result = [] gen = self.parent().gen for i in range(k): - exponent = exponent_syllable(g, i+1).sage() + exponent = exponent_syllable(g, i+1).sage() generator = gen(generator_syllable(g, i+1).sage() - 1) result.append( (generator, exponent) ) return tuple(result) @@ -729,8 +729,7 @@ def wrap_FreeGroup(libgap_free_group): assert libgap_free_group.IsFreeGroup() libgap_free_group._set_compare_by_id() names = tuple( str(g) for g in libgap_free_group.GeneratorsOfGroup() ) - # return FreeGroup_class(names, libgap_free_group) - return FreeGroup(names) + return FreeGroup_class(names, libgap_free_group) class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): @@ -881,7 +880,7 @@ def _element_constructor_(self, *args, **kwds): except AttributeError: return self.element_class(self, x, **kwds) if isinstance(P, FreeGroup_class): - names = {P._names[abs(i)-1] for i in x.Tietze()} + names = set(P._names[abs(i)-1] for i in x.Tietze()) if names.issubset(self._names): return self([i.sign()*(self._names.index(P._names[abs(i)-1])+1) for i in x.Tietze()]) From 11093b4e9102be324d9d7fc0019d30bdf2e93974 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sat, 20 Jan 2024 10:47:31 +0100 Subject: [PATCH 03/51] restore --- src/sage/groups/finitely_presented.py | 7 ++++--- src/sage/groups/free_group.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 9847b241371..6bfedd11ba5 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -411,11 +411,12 @@ def wrap_FpGroup(libgap_fpgroup): libgap_fpgroup._set_compare_by_id() from sage.groups.free_group import wrap_FreeGroup free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup()) - names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) + # names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) relations = tuple(free_group(rel.UnderlyingElement()) for rel in libgap_fpgroup.RelatorsOfFpGroup()) - relations_Tietze = tuple(rel.Tietze() for rel in relations) - return FreeGroup(names) / relations_Tietze + # relations_Tietze = tuple(rel.Tietze() for rel in relations) + # return FreeGroup(names) / relations_Tietze + return FinitelyPresentedGroup(free_group, relations) class RewritingSystem(): diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 6f3c4400779..211d32a5d01 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -728,7 +728,7 @@ def wrap_FreeGroup(libgap_free_group): """ assert libgap_free_group.IsFreeGroup() libgap_free_group._set_compare_by_id() - names = tuple( str(g) for g in libgap_free_group.GeneratorsOfGroup() ) + names = tuple(str(g) for g in libgap_free_group.GeneratorsOfGroup()) return FreeGroup_class(names, libgap_free_group) From 8abc7bfcd7a2157d9bef36c97da4539f097bfb2f Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sat, 20 Jan 2024 13:05:06 +0100 Subject: [PATCH 04/51] new __reduce__ for free groups --- src/sage/groups/finitely_presented.py | 15 +++++++++++++++ src/sage/groups/free_group.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 6bfedd11ba5..db03818707a 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -812,6 +812,21 @@ def __init__(self, free_group, relations, category=None): ParentLibGAP.__init__(self, parent_gap) Group.__init__(self, category=category) + # def __reduce__(self): + # """ + # Implement pickling. + # + # TESTS:: + # + # sage: F. = FreeGroup() + # sage: a.__reduce__() + # (Free Group on generators {a, b}, ((1,),)) + # sage: (a*b*a^-1).__reduce__() + # (Free Group on generators {a, b}, ((1, 2, -1),)) + # """ + # return (FinitelyPresentedGroup, (self._free_group, self._relations)) + + def _repr_(self): """ Return a string representation. diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 211d32a5d01..22248c70359 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -778,6 +778,21 @@ def __init__(self, generator_names, libgap_free_group=None): cat = Groups().Infinite() Group.__init__(self, category=cat) + def __reduce__(self): + """ + Implement pickling. + + TESTS:: + + sage: F. = FreeGroup() + sage: a.__reduce__() + (Free Group on generators {a, b}, ((1,),)) + sage: (a*b*a^-1).__reduce__() + (Free Group on generators {a, b}, ((1, 2, -1),)) + """ + from sage.structure.unique_representation import unreduce + return (unreduce, ((self._names, ), {})) + def _repr_(self): """ TESTS:: From e041db7db65b5d9a055ea8b21300b8747d5f5066 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sat, 20 Jan 2024 18:16:52 +0100 Subject: [PATCH 05/51] new reduce --- src/sage/groups/free_group.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 22248c70359..9e1d948f675 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -781,9 +781,9 @@ def __init__(self, generator_names, libgap_free_group=None): def __reduce__(self): """ Implement pickling. - + TESTS:: - + sage: F. = FreeGroup() sage: a.__reduce__() (Free Group on generators {a, b}, ((1,),)) @@ -791,7 +791,7 @@ def __reduce__(self): (Free Group on generators {a, b}, ((1, 2, -1),)) """ from sage.structure.unique_representation import unreduce - return (unreduce, ((self._names, ), {})) + return (unreduce, (FreeGroup_class, (self._names,), {})) def _repr_(self): """ From 18360f8228f163625bd4ba2eca64fbf9138cf99b Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sun, 21 Jan 2024 22:02:36 +0100 Subject: [PATCH 06/51] doctesting __reduce__ --- src/sage/groups/free_group.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 9e1d948f675..513c646fb61 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -781,14 +781,24 @@ def __init__(self, generator_names, libgap_free_group=None): def __reduce__(self): """ Implement pickling. - + TESTS:: - + sage: F. = FreeGroup() - sage: a.__reduce__() - (Free Group on generators {a, b}, ((1,),)) - sage: (a*b*a^-1).__reduce__() - (Free Group on generators {a, b}, ((1, 2, -1),)) + sage: F.__reduce__()[1] + (, + (('a', 'b'),), {}) + sage: from sage.groups.free_group import wrap_FreeGroup + sage: F1 = wrap_FreeGroup(libgap(F)) + sage: F1.__reduce__()[1] + (, + (('a', 'b'),), {}) + sage: save(F1,'F') + sage: F2 = load('F.sobj') + sage: F == F2 + True + sage: F1 == F2 + False """ from sage.structure.unique_representation import unreduce return (unreduce, (FreeGroup_class, (self._names,), {})) From d08581bca74ea60b8de367e9c3d73cc98d1dbc7e Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sun, 21 Jan 2024 22:14:28 +0100 Subject: [PATCH 07/51] undoing strange changes --- src/sage/groups/free_group.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 513c646fb61..770da77fb39 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -148,7 +148,7 @@ def _lexi_gen(zeroes=False): """ count = Integer(0) while True: - mwrap, ind = count.quo_rem(26) + mwrap, ind = count.quo_rem(26) if mwrap == 0 and not zeroes: name = '' else: @@ -505,12 +505,12 @@ def syllables(self): """ g = self.gap().UnderlyingElement() k = g.NumberSyllables().sage() - exponent_syllable = libgap.eval('ExponentSyllable') + exponent_syllable = libgap.eval('ExponentSyllable') generator_syllable = libgap.eval('GeneratorSyllable') result = [] gen = self.parent().gen for i in range(k): - exponent = exponent_syllable(g, i+1).sage() + exponent = exponent_syllable(g, i+1).sage() generator = gen(generator_syllable(g, i+1).sage() - 1) result.append( (generator, exponent) ) return tuple(result) @@ -905,7 +905,7 @@ def _element_constructor_(self, *args, **kwds): except AttributeError: return self.element_class(self, x, **kwds) if isinstance(P, FreeGroup_class): - names = set(P._names[abs(i)-1] for i in x.Tietze()) + names = {P._names[abs(i)-1] for i in x.Tietze()} if names.issubset(self._names): return self([i.sign()*(self._names.index(P._names[abs(i)-1])+1) for i in x.Tietze()]) From 83c74246fe43d950d240fa60624922c93cf011df Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sun, 21 Jan 2024 22:26:23 +0100 Subject: [PATCH 08/51] lint --- src/sage/groups/finitely_presented.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index db03818707a..94ce06b6843 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -815,9 +815,9 @@ def __init__(self, free_group, relations, category=None): # def __reduce__(self): # """ # Implement pickling. - # + # # TESTS:: - # + # # sage: F. = FreeGroup() # sage: a.__reduce__() # (Free Group on generators {a, b}, ((1,),)) @@ -826,7 +826,6 @@ def __init__(self, free_group, relations, category=None): # """ # return (FinitelyPresentedGroup, (self._free_group, self._relations)) - def _repr_(self): """ Return a string representation. From d045124efe30eb052bfbd13aa3cd70de7429e358 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Fri, 26 Jan 2024 08:19:35 +0100 Subject: [PATCH 09/51] eliminate wrap_FreeGroup --- src/sage/groups/finitely_presented.py | 14 +++++++------- src/sage/groups/free_group.py | 13 ++++--------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 94ce06b6843..33f7f897bef 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -409,14 +409,14 @@ def wrap_FpGroup(libgap_fpgroup): """ assert libgap_fpgroup.IsFpGroup() libgap_fpgroup._set_compare_by_id() - from sage.groups.free_group import wrap_FreeGroup - free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup()) - # names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) - relations = tuple(free_group(rel.UnderlyingElement()) + # from sage.groups.free_group import wrap_FreeGroup + # free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup()) + names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) + relations = tuple(rel.UnderlyingElement().LetterRepAssocWord() for rel in libgap_fpgroup.RelatorsOfFpGroup()) - # relations_Tietze = tuple(rel.Tietze() for rel in relations) - # return FreeGroup(names) / relations_Tietze - return FinitelyPresentedGroup(free_group, relations) + relations_Tietze = [[int(i) for i in rel] for rel in relations] + return FreeGroup(names) / relations_Tietze + # return FinitelyPresentedGroup(free_group, relations) class RewritingSystem(): diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 770da77fb39..ae15028fb36 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -729,7 +729,7 @@ def wrap_FreeGroup(libgap_free_group): assert libgap_free_group.IsFreeGroup() libgap_free_group._set_compare_by_id() names = tuple(str(g) for g in libgap_free_group.GeneratorsOfGroup()) - return FreeGroup_class(names, libgap_free_group) + return FreeGroup_class(names) class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): @@ -747,7 +747,7 @@ class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): """ Element = FreeGroupElement - def __init__(self, generator_names, libgap_free_group=None): + def __init__(self, generator_names): """ Python constructor. @@ -756,10 +756,6 @@ def __init__(self, generator_names, libgap_free_group=None): - ``generator_names`` -- a tuple of strings. The names of the generators. - - ``libgap_free_group`` -- a LibGAP free group or ``None`` - (default). The LibGAP free group to wrap. If ``None``, a - suitable group will be constructed. - TESTS:: sage: G. = FreeGroup() # indirect doctest @@ -769,8 +765,7 @@ def __init__(self, generator_names, libgap_free_group=None): ('a', 'b') """ self._assign_names(generator_names) - if libgap_free_group is None: - libgap_free_group = libgap.FreeGroup(generator_names) + libgap_free_group = libgap.FreeGroup(generator_names) ParentLibGAP.__init__(self, libgap_free_group) if not generator_names: cat = Groups().Finite() @@ -798,7 +793,7 @@ def __reduce__(self): sage: F == F2 True sage: F1 == F2 - False + True """ from sage.structure.unique_representation import unreduce return (unreduce, (FreeGroup_class, (self._names,), {})) From d50472caf5904e332d3b6a460917bde0817a9aa7 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Fri, 26 Jan 2024 09:08:51 +0100 Subject: [PATCH 10/51] give up special __reduce__ --- src/sage/groups/finitely_presented.py | 5 ++- src/sage/groups/free_group.py | 48 +++++++++++++-------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 33f7f897bef..c46988363a2 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -412,10 +412,9 @@ def wrap_FpGroup(libgap_fpgroup): # from sage.groups.free_group import wrap_FreeGroup # free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup()) names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) - relations = tuple(rel.UnderlyingElement().LetterRepAssocWord() + relations = tuple(rel.UnderlyingElement().LetterRepAssocWord().sage() for rel in libgap_fpgroup.RelatorsOfFpGroup()) - relations_Tietze = [[int(i) for i in rel] for rel in relations] - return FreeGroup(names) / relations_Tietze + return FreeGroup(names) / relations # return FinitelyPresentedGroup(free_group, relations) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index ae15028fb36..02e12c03f83 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -773,30 +773,30 @@ def __init__(self, generator_names): cat = Groups().Infinite() Group.__init__(self, category=cat) - def __reduce__(self): - """ - Implement pickling. - - TESTS:: - - sage: F. = FreeGroup() - sage: F.__reduce__()[1] - (, - (('a', 'b'),), {}) - sage: from sage.groups.free_group import wrap_FreeGroup - sage: F1 = wrap_FreeGroup(libgap(F)) - sage: F1.__reduce__()[1] - (, - (('a', 'b'),), {}) - sage: save(F1,'F') - sage: F2 = load('F.sobj') - sage: F == F2 - True - sage: F1 == F2 - True - """ - from sage.structure.unique_representation import unreduce - return (unreduce, (FreeGroup_class, (self._names,), {})) + # def __reduce__(self): + # """ + # Implement pickling. + # + # TESTS:: + # + # sage: F. = FreeGroup() + # sage: F.__reduce__()[1] + # (, + # (('a', 'b'),), {}) + # sage: from sage.groups.free_group import wrap_FreeGroup + # sage: F1 = wrap_FreeGroup(libgap(F)) + # sage: F1.__reduce__()[1] + # (, + # (('a', 'b'),), {}) + # sage: save(F1,'F') + # sage: F2 = load('F.sobj') + # sage: F == F2 + # True + # sage: F1 == F2 + # True + # """ + # from sage.structure.unique_representation import unreduce + # return (unreduce, (FreeGroup_class, (self._names,), {})) def _repr_(self): """ From 7db0b788003fcfbf68faa59ca6e1b504abaf7540 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Fri, 26 Jan 2024 11:08:18 +0100 Subject: [PATCH 11/51] sage method for gap f.p. groups --- src/sage/libs/gap/element.pyx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index ab9a2860cb4..c998de47235 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1341,6 +1341,11 @@ cdef class GapElement(RingElement): sage: p.sage().parent() Fraction Field of Univariate Polynomial Ring in x over Integer Ring + sage: from sage.groups.free_group import FreeGroup + sage: G = libgap(FreeGroup(2) / [(1, 2, 2, 1)]) + sage: G == G.sage() + True + TESTS: Check :trac:`30496`:: @@ -1389,6 +1394,10 @@ cdef class GapElement(RingElement): x = R.gen() return x**val * R(num) / R(den) + elif self.IsFpGroup(): + from sage.groups.finitely_presented import wrap_FpGroup + return wrap_FpGroup(self) + elif self.IsList(): # May be a list-like collection of some other type of GapElements # that we can convert From 099942980ae1d38567735085d3b412fd0754f440 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Fri, 26 Jan 2024 12:58:08 +0100 Subject: [PATCH 12/51] less wrap_FpGroup --- src/sage/groups/finitely_presented.py | 4 ++-- src/sage/schemes/curves/zariski_vankampen.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index c46988363a2..2e3aeea0dc2 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -1398,7 +1398,7 @@ def abelianization_map(self): hom_ab_fp = ab_libgap.IsomorphismFpGroup() ab_libgap_fp = hom_ab_fp.Range() hom_simply = ab_libgap_fp.IsomorphismSimplifiedFpGroup() - ab = wrap_FpGroup(hom_simply.Range()) + ab = hom_simply.Range().sage() images = [] for f in self.gens(): f0 = hom_ab_libgap.Image(f) @@ -1507,7 +1507,7 @@ def simplification_isomorphism(self): Uses GAP. """ II = self.gap().IsomorphismSimplifiedFpGroup() - codomain = wrap_FpGroup(II.Range()) + codomain = II.Range().sage() phi = lambda x: codomain(II.ImageElm(x.gap())) HS = self.Hom(codomain) return GroupMorphismWithGensImages(HS, phi) diff --git a/src/sage/schemes/curves/zariski_vankampen.py b/src/sage/schemes/curves/zariski_vankampen.py index fa30e97085b..2ce632d1c59 100644 --- a/src/sage/schemes/curves/zariski_vankampen.py +++ b/src/sage/schemes/curves/zariski_vankampen.py @@ -49,7 +49,7 @@ from sage.geometry.voronoi_diagram import VoronoiDiagram from sage.graphs.graph import Graph from sage.groups.braid import BraidGroup -from sage.groups.finitely_presented import wrap_FpGroup +# from sage.groups.finitely_presented import wrap_FpGroup from sage.groups.free_group import FreeGroup from sage.groups.perm_gps.permgroup_named import SymmetricGroup from sage.libs.braiding import leftnormalform, rightnormalform @@ -1390,7 +1390,7 @@ def braid2rels(L): P.SetTzOptions(dic) P.TzGoGo() P.TzGoGo() - gb = wrap_FpGroup(P.FpGroupPresentation()) + gb = P.FpGroupPresentation().sage() U = [_.Tietze() for _ in gb.relations()] return U From 566c9e127113e9f991e1effafe2d609e03ceb816 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Fri, 26 Jan 2024 16:28:29 +0100 Subject: [PATCH 13/51] no more wrap_FpGroup --- src/sage/groups/finitely_presented.py | 93 ++++++++++++++------------- src/sage/libs/gap/element.pyx | 9 ++- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 2e3aeea0dc2..a02e42d52ed 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -370,52 +370,53 @@ def __call__(self, *values, **kwds): return super().__call__(values) -def wrap_FpGroup(libgap_fpgroup): - """ - Wrap a GAP finitely presented group. - - This function changes the comparison method of - ``libgap_free_group`` to comparison by Python ``id``. If you want - to put the LibGAP free group into a container ``(set, dict)`` then you - should understand the implications of - :meth:`~sage.libs.gap.element.GapElement._set_compare_by_id`. To - be safe, it is recommended that you just work with the resulting - Sage :class:`FinitelyPresentedGroup`. - - INPUT: - - - ``libgap_fpgroup`` -- a LibGAP finitely presented group - - OUTPUT: - - A Sage :class:`FinitelyPresentedGroup`. - - EXAMPLES: - - First construct a LibGAP finitely presented group:: - - sage: F = libgap.FreeGroup(['a', 'b']) - sage: a_cubed = F.GeneratorsOfGroup()[0] ^ 3 - sage: P = F / libgap([ a_cubed ]); P - - sage: type(P) - - - Now wrap it:: - - sage: from sage.groups.finitely_presented import wrap_FpGroup - sage: wrap_FpGroup(P) - Finitely presented group < a, b | a^3 > - """ - assert libgap_fpgroup.IsFpGroup() - libgap_fpgroup._set_compare_by_id() - # from sage.groups.free_group import wrap_FreeGroup - # free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup()) - names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) - relations = tuple(rel.UnderlyingElement().LetterRepAssocWord().sage() - for rel in libgap_fpgroup.RelatorsOfFpGroup()) - return FreeGroup(names) / relations - # return FinitelyPresentedGroup(free_group, relations) +# def wrap_FpGroup(libgap_fpgroup): +# """ +# Wrap a GAP finitely presented group. +# +# This function changes the comparison method of +# ``libgap_free_group`` to comparison by Python ``id``. If you want +# to put the LibGAP free group into a container ``(set, dict)`` then you +# should understand the implications of +# :meth:`~sage.libs.gap.element.GapElement._set_compare_by_id`. To +# be safe, it is recommended that you just work with the resulting +# Sage :class:`FinitelyPresentedGroup`. +# +# INPUT: +# +# - ``libgap_fpgroup`` -- a LibGAP finitely presented group +# +# OUTPUT: +# +# A Sage :class:`FinitelyPresentedGroup`. +# +# EXAMPLES: +# +# First construct a LibGAP finitely presented group:: +# +# sage: F = libgap.FreeGroup(['a', 'b']) +# sage: a_cubed = F.GeneratorsOfGroup()[0] ^ 3 +# sage: P = F / libgap([ a_cubed ]); P +# +# sage: type(P) +# +# +# Now wrap it:: +# +# sage: from sage.groups.finitely_presented import wrap_FpGroup +# sage: wrap_FpGroup(P) +# Finitely presented group < a, b | a^3 > +# """ +# assert libgap_fpgroup.IsFpGroup() +# libgap_fpgroup._set_compare_by_id() +# # from sage.groups.free_group import wrap_FreeGroup +# # free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup()) +# names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) +# F = FreeGroup(names) +# relations = tuple(F(rel.UnderlyingElement().LetterRepAssocWord().sage()) +# for rel in libgap_fpgroup.RelatorsOfFpGroup()) +# G = FinitelyPresentedGroup(F, relations) +# return G class RewritingSystem(): diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index c998de47235..79af8808a44 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1395,8 +1395,13 @@ cdef class GapElement(RingElement): return x**val * R(num) / R(den) elif self.IsFpGroup(): - from sage.groups.finitely_presented import wrap_FpGroup - return wrap_FpGroup(self) + from sage.groups.free_group import FreeGroup + self._set_compare_by_id() + names = tuple(str(g) for g in self.FreeGroupOfFpGroup().GeneratorsOfGroup()) + F = FreeGroup(names) + relations = tuple(rel.UnderlyingElement().LetterRepAssocWord().sage() + for rel in self.RelatorsOfFpGroup()) + return F / relations elif self.IsList(): # May be a list-like collection of some other type of GapElements From d8c43248e3f20d64abff2e993e0bbaae3294ded8 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sun, 28 Jan 2024 16:49:32 +0100 Subject: [PATCH 14/51] trying... --- src/sage/groups/finitely_presented.py | 41 +++++++++++++++------------ src/sage/groups/free_group.py | 38 +++++++------------------ src/sage/libs/gap/element.pyx | 6 ++-- 3 files changed, 37 insertions(+), 48 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index a02e42d52ed..0312c04caf0 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -783,7 +783,7 @@ class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, Group, Pare """ Element = FinitelyPresentedGroupElement - def __init__(self, free_group, relations, category=None): + def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): """ The Python constructor. @@ -808,23 +808,26 @@ def __init__(self, free_group, relations, category=None): self._free_group = free_group self._relations = relations self._assign_names(free_group.variable_names()) + if libgap_fpgroup is None: + parent_gap = libgap_fpgroup parent_gap = free_group.gap() / libgap([rel.gap() for rel in relations]) ParentLibGAP.__init__(self, parent_gap) Group.__init__(self, category=category) - # def __reduce__(self): - # """ - # Implement pickling. - # - # TESTS:: - # - # sage: F. = FreeGroup() - # sage: a.__reduce__() - # (Free Group on generators {a, b}, ((1,),)) - # sage: (a*b*a^-1).__reduce__() - # (Free Group on generators {a, b}, ((1, 2, -1),)) - # """ - # return (FinitelyPresentedGroup, (self._free_group, self._relations)) + def __reduce__(self): + """ + Implement pickling. + + TESTS:: + + sage: F. = FreeGroup() + sage: a.__reduce__() + (Free Group on generators {a, b}, ((1,),)) + sage: (a*b*a^-1).__reduce__() + (Free Group on generators {a, b}, ((1, 2, -1),)) + """ + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._free_group, self._relations), {})) def _repr_(self): """ @@ -1508,10 +1511,12 @@ def simplification_isomorphism(self): Uses GAP. """ II = self.gap().IsomorphismSimplifiedFpGroup() - codomain = II.Range().sage() - phi = lambda x: codomain(II.ImageElm(x.gap())) - HS = self.Hom(codomain) - return GroupMorphismWithGensImages(HS, phi) + cod = II.Range().sage() + # phi = [cod(II.ImageElm(x)) for x in self.gap().GeneratorsOfGroup()] + phi = lambda x: cod(II.ImageElm(x.gap())) + HS = self.Hom(cod) + h = GroupMorphismWithGensImages(HS, phi) + return h # self.hom(codomain=cod, im_gens=[h(x) for x in self.gens()]) def simplified(self): """ diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 02e12c03f83..187873290b9 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -747,7 +747,7 @@ class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): """ Element = FreeGroupElement - def __init__(self, generator_names): + def __init__(self, generator_names, libgap_free_group=None): """ Python constructor. @@ -756,6 +756,10 @@ def __init__(self, generator_names): - ``generator_names`` -- a tuple of strings. The names of the generators. + - ``libgap_free_group`` -- a LibGAP free group or ``None`` + (default). The LibGAP free group to wrap. If ``None``, a + suitable group will be constructed. + TESTS:: sage: G. = FreeGroup() # indirect doctest @@ -765,7 +769,8 @@ def __init__(self, generator_names): ('a', 'b') """ self._assign_names(generator_names) - libgap_free_group = libgap.FreeGroup(generator_names) + if not libgap_free_group: + libgap_free_group = libgap.FreeGroup(generator_names) ParentLibGAP.__init__(self, libgap_free_group) if not generator_names: cat = Groups().Finite() @@ -773,31 +778,10 @@ def __init__(self, generator_names): cat = Groups().Infinite() Group.__init__(self, category=cat) - # def __reduce__(self): - # """ - # Implement pickling. - # - # TESTS:: - # - # sage: F. = FreeGroup() - # sage: F.__reduce__()[1] - # (, - # (('a', 'b'),), {}) - # sage: from sage.groups.free_group import wrap_FreeGroup - # sage: F1 = wrap_FreeGroup(libgap(F)) - # sage: F1.__reduce__()[1] - # (, - # (('a', 'b'),), {}) - # sage: save(F1,'F') - # sage: F2 = load('F.sobj') - # sage: F == F2 - # True - # sage: F1 == F2 - # True - # """ - # from sage.structure.unique_representation import unreduce - # return (unreduce, (FreeGroup_class, (self._names,), {})) - + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._names, ), {}) +) def _repr_(self): """ TESTS:: diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 79af8808a44..a2b1c2229fc 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1396,12 +1396,12 @@ cdef class GapElement(RingElement): elif self.IsFpGroup(): from sage.groups.free_group import FreeGroup + from sage.groups.finitely_presented import FinitelyPresentedGroup self._set_compare_by_id() names = tuple(str(g) for g in self.FreeGroupOfFpGroup().GeneratorsOfGroup()) F = FreeGroup(names) - relations = tuple(rel.UnderlyingElement().LetterRepAssocWord().sage() - for rel in self.RelatorsOfFpGroup()) - return F / relations + relations = tuple(F(rel.LetterRepAssocWord().sage()) for rel in self.RelatorsOfFpGroup()) + return FinitelyPresentedGroup(F, relations, libgap_fpgroup=self) elif self.IsList(): # May be a list-like collection of some other type of GapElements From 1bea8618e6a3455b27844a4c968c6d7ac9cc517f Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sun, 28 Jan 2024 18:17:08 +0100 Subject: [PATCH 15/51] changing sage method for gap f.p. groups and hom --- src/sage/groups/artin.py | 8 ++- src/sage/groups/cubic_braid.py | 7 ++- src/sage/groups/finitely_presented.py | 87 +++++++++++++-------------- src/sage/groups/raag.py | 5 ++ 4 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index 2b2f604a462..78ee82f881a 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -472,7 +472,9 @@ def __init__(self, coxeter_matrix, names): sage: A = ArtinGroup(['B',3], ['x','y','z']) sage: TestSuite(A).run() """ - self._coxeter_group = CoxeterGroup(coxeter_matrix) + self._names = names + self._coxeter_matrix = coxeter_matrix + self._coxeter_group = CoxeterGroup(self._coxeter_matrix) free_group = FreeGroup(names) rels = [] # Generate the relations based on the Coxeter graph @@ -488,6 +490,10 @@ def __init__(self, coxeter_matrix, names): rels.append(free_group(elt)) FinitelyPresentedGroup.__init__(self, free_group, tuple(rels)) + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._coxeter_matrix, self._names), {})) + def _repr_(self): """ Return a string representation of ``self``. diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index c8119f12dbc..52707a2e0c5 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -750,7 +750,7 @@ def __init__(self, names, cbg_type=None): n = Integer(len(names)) if n < 1: raise ValueError("the number of strands must be an integer larger than one") - + self._cbg_type_orig = cbg_type if cbg_type is None: cbg_type = CubicBraidGroup.type.Coxeter if not isinstance(cbg_type, CubicBraidGroup.type): @@ -791,6 +791,7 @@ def __init__(self, names, cbg_type=None): cat = Groups().Infinite() FinitelyPresentedGroup.__init__(self, free_group, tuple(rels), category=cat) self._free_group = free_group + self._names = names # ------------------------------------------------------------------------------------------------ # the following global pointers to classical group realizations will be set in the private method @@ -804,6 +805,10 @@ def __init__(self, names, cbg_type=None): self._centralizing_element = None # image under nat. map of the former one in the proj. classical group return + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._names,), {'cbg_type': self._cbg_type_orig})) + def _repr_(self): r""" Return a string representation. diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 0312c04caf0..492612567f0 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -808,9 +808,10 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): self._free_group = free_group self._relations = relations self._assign_names(free_group.variable_names()) - if libgap_fpgroup is None: + if libgap_fpgroup is not None: parent_gap = libgap_fpgroup - parent_gap = free_group.gap() / libgap([rel.gap() for rel in relations]) + else: + parent_gap = free_group.gap() / libgap([rel.gap() for rel in relations]) ParentLibGAP.__init__(self, parent_gap) Group.__init__(self, category=category) @@ -1410,7 +1411,7 @@ def abelianization_map(self): f2 = hom_simply.Image(f1) L = f2.UnderlyingElement().LetterRepAssocWord() images.append(ab([int(j) for j in L])) - return self.hom(codomain=ab, im_gens=images) + return self.hom(codomain=ab, im_gens=images, check=False) @cached_method def abelianization_to_algebra(self, ring=QQ): @@ -1483,12 +1484,9 @@ def simplification_isomorphism(self): sage: H = G / [a*b*c, a*b^2, c*b/c^2] sage: I = H.simplification_isomorphism() sage: I - Generic morphism: + Group morphism: From: Finitely presented group < a, b, c | a*b*c, a*b^2, c*b*c^-2 > To: Finitely presented group < b | > - Defn: a |--> b^-2 - b |--> b - c |--> b sage: I(a) b^-2 sage: I(b) @@ -1500,11 +1498,12 @@ def simplification_isomorphism(self): sage: F = FreeGroup(1) sage: G = F.quotient([F.0]) - sage: G.simplification_isomorphism() - Generic morphism: + sage: h = G.simplification_isomorphism(); h + Group morphism: From: Finitely presented group < x | x > To: Finitely presented group < | > - Defn: x |--> 1 + sage: h(G.gen(0)) + 1 ALGORITHM: @@ -1512,11 +1511,13 @@ def simplification_isomorphism(self): """ II = self.gap().IsomorphismSimplifiedFpGroup() cod = II.Range().sage() - # phi = [cod(II.ImageElm(x)) for x in self.gap().GeneratorsOfGroup()] - phi = lambda x: cod(II.ImageElm(x.gap())) - HS = self.Hom(cod) - h = GroupMorphismWithGensImages(HS, phi) - return h # self.hom(codomain=cod, im_gens=[h(x) for x in self.gens()]) + phi = [cod(II.ImageElm(x)) for x in self.gap().GeneratorsOfGroup()] + return self.hom(codomain=cod, im_gens=phi, check=False) + # II = self.gap().IsomorphismSimplifiedFpGroup() + # codomain = II.Range().sage() + # phi = lambda x: codomain(II.ImageElm(x.gap())) + # HS = self.Hom(codomain) + # return GroupMorphismWithGensImages(HS, phi) def simplified(self): """ @@ -1592,48 +1593,44 @@ def epimorphisms(self, H): sage: F = FreeGroup(3) sage: G = F / [F([1, 2, 3, 1, 2, 3]), F([1, 1, 1])] sage: H = AlternatingGroup(3) - sage: G.epimorphisms(H) - [Generic morphism: - From: Finitely presented group < x0, x1, x2 | x0*x1*x2*x0*x1*x2, x0^3 > - To: Alternating group of order 3!/2 as a permutation group - Defn: x0 |--> () - x1 |--> (1,3,2) - x2 |--> (1,2,3), - Generic morphism: - From: Finitely presented group < x0, x1, x2 | x0*x1*x2*x0*x1*x2, x0^3 > - To: Alternating group of order 3!/2 as a permutation group - Defn: x0 |--> (1,3,2) - x1 |--> () - x2 |--> (1,2,3), - Generic morphism: - From: Finitely presented group < x0, x1, x2 | x0*x1*x2*x0*x1*x2, x0^3 > - To: Alternating group of order 3!/2 as a permutation group - Defn: x0 |--> (1,3,2) - x1 |--> (1,2,3) - x2 |--> (), - Generic morphism: - From: Finitely presented group < x0, x1, x2 | x0*x1*x2*x0*x1*x2, x0^3 > - To: Alternating group of order 3!/2 as a permutation group - Defn: x0 |--> (1,2,3) - x1 |--> (1,2,3) - x2 |--> (1,2,3)] + sage: for quo in G.epimorphisms(H): + ....: for a in G.gens(): + ....: print(a, "|-->", quo(a)) + ....: print("-----") + x0 |--> () + x1 |--> (1,3,2) + x2 |--> (1,2,3) + ----- + x0 |--> (1,3,2) + x1 |--> () + x2 |--> (1,2,3) + ----- + x0 |--> (1,3,2) + x1 |--> (1,2,3) + x2 |--> () + ----- + x0 |--> (1,2,3) + x1 |--> (1,2,3) + x2 |--> (1,2,3) + ----- ALGORITHM: Uses libgap's GQuotients function. """ - from sage.misc.misc_c import prod - HomSpace = self.Hom(H) + # from sage.misc.misc_c import prod + # HomSpace = self.Hom(H) Gg = libgap(self) Hg = libgap(H) gquotients = Gg.GQuotients(Hg) res = [] # the following closure is needed to attach a specific value of quo to # each function in the different morphisms - fmap = lambda tup: (lambda a: H(prod(tup[abs(i)-1]**sign(i) for i in a.Tietze()))) + # fmap = lambda tup: (lambda a: H(prod(tup[abs(i)-1]**sign(i) for i in a.Tietze()))) for quo in gquotients: - tup = tuple(H(quo.ImageElm(i.gap()).sage()) for i in self.gens()) - fhom = GroupMorphismWithGensImages(HomSpace, fmap(tup)) + # tup = tuple(H(quo.ImageElm(i.gap()).sage()) for i in self.gens()) + # fhom = GroupMorphismWithGensImages(HomSpace, fmap(tup)) + fhom = self.hom(codomain=H, im_gens=[H(quo.ImageElm(a.gap())) for a in self.gens()]) res.append(fhom) return res diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index ff7aa0ae9c2..3e5091baf1b 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -189,6 +189,7 @@ def __init__(self, G, names): Category of infinite groups """ self._graph = G + self._names = names F = FreeGroup(names=names) CG = Graph(G).complement() # Make sure it's mutable CG.relabel() # Standardize the labels @@ -204,6 +205,10 @@ def __init__(self, G, names): FinitelyPresentedGroup.__init__(self, F, rels, category=Groups().Infinite()) + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._graph, self._names), {})) + def _repr_(self) -> str: """ Return a string representation of ``self``. From d2ffc5cedd1a927a706ecded5113f877e99f471e Mon Sep 17 00:00:00 2001 From: Enrique Manuel Artal Bartolo Date: Mon, 29 Jan 2024 08:38:27 +0100 Subject: [PATCH 16/51] Update free_group.py typos and unnecessary wrap_FreeGroup --- src/sage/groups/free_group.py | 54 ++--------------------------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 187873290b9..f3ad48698e6 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -684,54 +684,6 @@ def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds): return FreeGroup_class(names) -def wrap_FreeGroup(libgap_free_group): - """ - Wrap a LibGAP free group. - - This function changes the comparison method of - ``libgap_free_group`` to comparison by Python ``id``. If you want - to put the LibGAP free group into a container (set, dict) then you - should understand the implications of - :meth:`~sage.libs.gap.element.GapElement._set_compare_by_id`. To - be safe, it is recommended that you just work with the resulting - Sage :class:`FreeGroup_class`. - - INPUT: - - - ``libgap_free_group`` -- a LibGAP free group. - - OUTPUT: - - A Sage :class:`FreeGroup_class`. - - EXAMPLES: - - First construct a LibGAP free group:: - - sage: F = libgap.FreeGroup(['a', 'b']) - sage: type(F) - - - Now wrap it:: - - sage: from sage.groups.free_group import wrap_FreeGroup - sage: wrap_FreeGroup(F) - Free Group on generators {a, b} - - TESTS: - - Check that we can do it twice (see :trac:`12339`) :: - - sage: G = libgap.FreeGroup(['a', 'b']) - sage: wrap_FreeGroup(G) - Free Group on generators {a, b} - """ - assert libgap_free_group.IsFreeGroup() - libgap_free_group._set_compare_by_id() - names = tuple(str(g) for g in libgap_free_group.GeneratorsOfGroup()) - return FreeGroup_class(names) - - class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's FreeGroup @@ -769,7 +721,7 @@ def __init__(self, generator_names, libgap_free_group=None): ('a', 'b') """ self._assign_names(generator_names) - if not libgap_free_group: + if libgap_free_group is None: libgap_free_group = libgap.FreeGroup(generator_names) ParentLibGAP.__init__(self, libgap_free_group) if not generator_names: @@ -780,8 +732,8 @@ def __init__(self, generator_names, libgap_free_group=None): def __reduce__(self): from sage.structure.unique_representation import unreduce - return (unreduce, (self.__class__.__base__, (self._names, ), {}) -) + return (unreduce, (self.__class__.__base__, (self._names, ), {})) + def _repr_(self): """ TESTS:: From 70f5ea3f1e6f6f6e6d67ccc71d1c1bba32e17b9a Mon Sep 17 00:00:00 2001 From: Enrique Manuel Artal Bartolo Date: Mon, 29 Jan 2024 08:43:24 +0100 Subject: [PATCH 17/51] Update finitely_presented.py --- src/sage/groups/finitely_presented.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 492612567f0..0ebccdd0b0c 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -808,11 +808,9 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): self._free_group = free_group self._relations = relations self._assign_names(free_group.variable_names()) - if libgap_fpgroup is not None: - parent_gap = libgap_fpgroup - else: - parent_gap = free_group.gap() / libgap([rel.gap() for rel in relations]) - ParentLibGAP.__init__(self, parent_gap) + if libgap_fpgroup is None: + libgap_fpgroup = free_group.gap() / libgap([rel.gap() for rel in relations]) + ParentLibGAP.__init__(self, libgap_fpgroup) Group.__init__(self, category=category) def __reduce__(self): From 2c7152934d2d3cdc5c476bdba92eff2408b4fe82 Mon Sep 17 00:00:00 2001 From: Enrique Manuel Artal Bartolo Date: Mon, 29 Jan 2024 08:50:19 +0100 Subject: [PATCH 18/51] Update free_group.py Lint --- src/sage/groups/free_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index f3ad48698e6..a6b35a60298 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -733,7 +733,7 @@ def __init__(self, generator_names, libgap_free_group=None): def __reduce__(self): from sage.structure.unique_representation import unreduce return (unreduce, (self.__class__.__base__, (self._names, ), {})) - + def _repr_(self): """ TESTS:: From e9222c47428371306bae97757817e2fd6970932e Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 29 Jan 2024 08:55:24 +0100 Subject: [PATCH 19/51] Added gap groups for free and f.p groups, added __reduce__ methods for these groups and also for artin, cubicbraid and raag --- src/sage/groups/artin.py | 6 ++ src/sage/groups/cubic_braid.py | 7 ++- src/sage/groups/finitely_presented.py | 32 +++++----- src/sage/groups/free_group.py | 84 ++++----------------------- src/sage/groups/raag.py | 5 ++ src/sage/libs/gap/element.pyx | 7 ++- 6 files changed, 48 insertions(+), 93 deletions(-) diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index 2b2f604a462..dd48f51b708 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -472,6 +472,8 @@ def __init__(self, coxeter_matrix, names): sage: A = ArtinGroup(['B',3], ['x','y','z']) sage: TestSuite(A).run() """ + self._names = names + self._coxeter_matrix = coxeter_matrix self._coxeter_group = CoxeterGroup(coxeter_matrix) free_group = FreeGroup(names) rels = [] @@ -488,6 +490,10 @@ def __init__(self, coxeter_matrix, names): rels.append(free_group(elt)) FinitelyPresentedGroup.__init__(self, free_group, tuple(rels)) + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._coxeter_matrix, self._names), {})) + def _repr_(self): """ Return a string representation of ``self``. diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index c8119f12dbc..52707a2e0c5 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -750,7 +750,7 @@ def __init__(self, names, cbg_type=None): n = Integer(len(names)) if n < 1: raise ValueError("the number of strands must be an integer larger than one") - + self._cbg_type_orig = cbg_type if cbg_type is None: cbg_type = CubicBraidGroup.type.Coxeter if not isinstance(cbg_type, CubicBraidGroup.type): @@ -791,6 +791,7 @@ def __init__(self, names, cbg_type=None): cat = Groups().Infinite() FinitelyPresentedGroup.__init__(self, free_group, tuple(rels), category=cat) self._free_group = free_group + self._names = names # ------------------------------------------------------------------------------------------------ # the following global pointers to classical group realizations will be set in the private method @@ -804,6 +805,10 @@ def __init__(self, names, cbg_type=None): self._centralizing_element = None # image under nat. map of the former one in the proj. classical group return + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._names,), {'cbg_type': self._cbg_type_orig})) + def _repr_(self): r""" Return a string representation. diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index a02e42d52ed..4212b738c7b 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -783,7 +783,7 @@ class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, Group, Pare """ Element = FinitelyPresentedGroupElement - def __init__(self, free_group, relations, category=None): + def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): """ The Python constructor. @@ -808,23 +808,23 @@ def __init__(self, free_group, relations, category=None): self._free_group = free_group self._relations = relations self._assign_names(free_group.variable_names()) - parent_gap = free_group.gap() / libgap([rel.gap() for rel in relations]) - ParentLibGAP.__init__(self, parent_gap) + if libgap_fpgroup is None: + libgap_fpgroup = free_group.gap() / libgap([rel.gap() for rel in relations]) + ParentLibGAP.__init__(self, libgap_fpgroup) Group.__init__(self, category=category) - # def __reduce__(self): - # """ - # Implement pickling. - # - # TESTS:: - # - # sage: F. = FreeGroup() - # sage: a.__reduce__() - # (Free Group on generators {a, b}, ((1,),)) - # sage: (a*b*a^-1).__reduce__() - # (Free Group on generators {a, b}, ((1, 2, -1),)) - # """ - # return (FinitelyPresentedGroup, (self._free_group, self._relations)) + def __reduce__(self): + """ + Implement pickling. + TESTS:: + sage: F. = FreeGroup() + sage: a.__reduce__() + (Free Group on generators {a, b}, ((1,),)) + sage: (a*b*a^-1).__reduce__() + (Free Group on generators {a, b}, ((1, 2, -1),)) + """ + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._free_group, self._relations), {})) def _repr_(self): """ diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 02e12c03f83..a6b35a60298 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -684,54 +684,6 @@ def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds): return FreeGroup_class(names) -def wrap_FreeGroup(libgap_free_group): - """ - Wrap a LibGAP free group. - - This function changes the comparison method of - ``libgap_free_group`` to comparison by Python ``id``. If you want - to put the LibGAP free group into a container (set, dict) then you - should understand the implications of - :meth:`~sage.libs.gap.element.GapElement._set_compare_by_id`. To - be safe, it is recommended that you just work with the resulting - Sage :class:`FreeGroup_class`. - - INPUT: - - - ``libgap_free_group`` -- a LibGAP free group. - - OUTPUT: - - A Sage :class:`FreeGroup_class`. - - EXAMPLES: - - First construct a LibGAP free group:: - - sage: F = libgap.FreeGroup(['a', 'b']) - sage: type(F) - - - Now wrap it:: - - sage: from sage.groups.free_group import wrap_FreeGroup - sage: wrap_FreeGroup(F) - Free Group on generators {a, b} - - TESTS: - - Check that we can do it twice (see :trac:`12339`) :: - - sage: G = libgap.FreeGroup(['a', 'b']) - sage: wrap_FreeGroup(G) - Free Group on generators {a, b} - """ - assert libgap_free_group.IsFreeGroup() - libgap_free_group._set_compare_by_id() - names = tuple(str(g) for g in libgap_free_group.GeneratorsOfGroup()) - return FreeGroup_class(names) - - class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's FreeGroup @@ -747,7 +699,7 @@ class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): """ Element = FreeGroupElement - def __init__(self, generator_names): + def __init__(self, generator_names, libgap_free_group=None): """ Python constructor. @@ -756,6 +708,10 @@ def __init__(self, generator_names): - ``generator_names`` -- a tuple of strings. The names of the generators. + - ``libgap_free_group`` -- a LibGAP free group or ``None`` + (default). The LibGAP free group to wrap. If ``None``, a + suitable group will be constructed. + TESTS:: sage: G. = FreeGroup() # indirect doctest @@ -765,7 +721,8 @@ def __init__(self, generator_names): ('a', 'b') """ self._assign_names(generator_names) - libgap_free_group = libgap.FreeGroup(generator_names) + if libgap_free_group is None: + libgap_free_group = libgap.FreeGroup(generator_names) ParentLibGAP.__init__(self, libgap_free_group) if not generator_names: cat = Groups().Finite() @@ -773,30 +730,9 @@ def __init__(self, generator_names): cat = Groups().Infinite() Group.__init__(self, category=cat) - # def __reduce__(self): - # """ - # Implement pickling. - # - # TESTS:: - # - # sage: F. = FreeGroup() - # sage: F.__reduce__()[1] - # (, - # (('a', 'b'),), {}) - # sage: from sage.groups.free_group import wrap_FreeGroup - # sage: F1 = wrap_FreeGroup(libgap(F)) - # sage: F1.__reduce__()[1] - # (, - # (('a', 'b'),), {}) - # sage: save(F1,'F') - # sage: F2 = load('F.sobj') - # sage: F == F2 - # True - # sage: F1 == F2 - # True - # """ - # from sage.structure.unique_representation import unreduce - # return (unreduce, (FreeGroup_class, (self._names,), {})) + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._names, ), {})) def _repr_(self): """ diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index ff7aa0ae9c2..3e5091baf1b 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -189,6 +189,7 @@ def __init__(self, G, names): Category of infinite groups """ self._graph = G + self._names = names F = FreeGroup(names=names) CG = Graph(G).complement() # Make sure it's mutable CG.relabel() # Standardize the labels @@ -204,6 +205,10 @@ def __init__(self, G, names): FinitelyPresentedGroup.__init__(self, F, rels, category=Groups().Infinite()) + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._graph, self._names), {})) + def _repr_(self) -> str: """ Return a string representation of ``self``. diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 79af8808a44..ce22c7ef7ba 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1396,12 +1396,15 @@ cdef class GapElement(RingElement): elif self.IsFpGroup(): from sage.groups.free_group import FreeGroup + from sage.groups.finitely_presented import FinitelyPresentedGroup self._set_compare_by_id() names = tuple(str(g) for g in self.FreeGroupOfFpGroup().GeneratorsOfGroup()) F = FreeGroup(names) - relations = tuple(rel.UnderlyingElement().LetterRepAssocWord().sage() + + relations = tuple(F(rel.LetterRepAssocWord().sage()) for rel in self.RelatorsOfFpGroup()) - return F / relations + return FinitelyPresentedGroup(F, relations, libgap_fpgroup=self) + elif self.IsList(): # May be a list-like collection of some other type of GapElements From 86523153902f2518afd707e0c036ce0364d248d4 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 29 Jan 2024 09:03:46 +0100 Subject: [PATCH 20/51] lint --- src/sage/groups/finitely_presented.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 4212b738c7b..673a77697cb 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -816,6 +816,7 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): def __reduce__(self): """ Implement pickling. + TESTS:: sage: F. = FreeGroup() sage: a.__reduce__() From b840c3573b9cd3d66474719236616dad18bfa9e0 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 29 Jan 2024 17:23:34 +0100 Subject: [PATCH 21/51] if the sage and gap generators are permuted, hom are correct --- src/sage/groups/libgap_morphism.py | 7 ++++++- src/sage/libs/gap/element.pyx | 11 +++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/sage/groups/libgap_morphism.py b/src/sage/groups/libgap_morphism.py index f1a47fcb279..4f3a4049cd6 100644 --- a/src/sage/groups/libgap_morphism.py +++ b/src/sage/groups/libgap_morphism.py @@ -682,7 +682,12 @@ def _element_constructor_(self, x, check=True, **options): dom = self.domain() codom = self.codomain() gens = dom.gap().GeneratorsOfGroup() - imgs = [codom(g).gap() for g in x] + gens0 = [a for a in gens] + imgs = [] + for g in dom.gens(): + j = gens0.index(g.gap()) + imgs.append(codom(x[j]).gap()) + # imgs = [codom(g).gap() for g in x] if check: if not len(gens) == len(imgs): raise ValueError("provide an image for each generator") diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index bda40784a2c..82cb6ab0075 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1341,9 +1341,12 @@ cdef class GapElement(RingElement): sage: p.sage().parent() Fraction Field of Univariate Polynomial Ring in x over Integer Ring - sage: from sage.groups.free_group import FreeGroup - sage: G = libgap(FreeGroup(2) / [(1, 2, 2, 1)]) - sage: G == G.sage() + sage: G = libgap.SymplecticGroup(2,2).IsomorphismFpGroup().Range() + sage: G1 = G.sage(); G1 + Finitely presented group < F1, F2 | F1^2, F2^2, (F1*F2)^3 > + sage: G1.gap() == G + True + sage: G1.is_isomorphic(SymmetricGroup(3)) True TESTS: @@ -2451,7 +2454,7 @@ cdef class GapElement_Function(GapElement): [Group(()), Sym( [ 1 .. 4 ] ), Alt( [ 1 .. 4 ] ), - Group([ (1,4)(2,3), (1,2)(3,4) ])] + Group([ (1,4)(2,3), (1,3)(2,4) ])] sage: libgap.eval("a := NormalSubgroups") From 58f2430fde4719ca066355e27d668d1866ed4ecf Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 29 Jan 2024 19:20:45 +0100 Subject: [PATCH 22/51] restore --- src/sage/groups/libgap_morphism.py | 7 +------ src/sage/libs/gap/element.pyx | 11 ++++------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/sage/groups/libgap_morphism.py b/src/sage/groups/libgap_morphism.py index 4f3a4049cd6..f1a47fcb279 100644 --- a/src/sage/groups/libgap_morphism.py +++ b/src/sage/groups/libgap_morphism.py @@ -682,12 +682,7 @@ def _element_constructor_(self, x, check=True, **options): dom = self.domain() codom = self.codomain() gens = dom.gap().GeneratorsOfGroup() - gens0 = [a for a in gens] - imgs = [] - for g in dom.gens(): - j = gens0.index(g.gap()) - imgs.append(codom(x[j]).gap()) - # imgs = [codom(g).gap() for g in x] + imgs = [codom(g).gap() for g in x] if check: if not len(gens) == len(imgs): raise ValueError("provide an image for each generator") diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 82cb6ab0075..bda40784a2c 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1341,12 +1341,9 @@ cdef class GapElement(RingElement): sage: p.sage().parent() Fraction Field of Univariate Polynomial Ring in x over Integer Ring - sage: G = libgap.SymplecticGroup(2,2).IsomorphismFpGroup().Range() - sage: G1 = G.sage(); G1 - Finitely presented group < F1, F2 | F1^2, F2^2, (F1*F2)^3 > - sage: G1.gap() == G - True - sage: G1.is_isomorphic(SymmetricGroup(3)) + sage: from sage.groups.free_group import FreeGroup + sage: G = libgap(FreeGroup(2) / [(1, 2, 2, 1)]) + sage: G == G.sage() True TESTS: @@ -2454,7 +2451,7 @@ cdef class GapElement_Function(GapElement): [Group(()), Sym( [ 1 .. 4 ] ), Alt( [ 1 .. 4 ] ), - Group([ (1,4)(2,3), (1,3)(2,4) ])] + Group([ (1,4)(2,3), (1,2)(3,4) ])] sage: libgap.eval("a := NormalSubgroups") From dcd21137ddb1c69293f45ee1b534a1f5065c2e28 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 29 Jan 2024 23:35:07 +0100 Subject: [PATCH 23/51] change order of generators in Alternating groups to match the order in gap --- src/sage/categories/finite_groups.py | 6 +++--- src/sage/categories/groups.py | 4 ++-- src/sage/categories/monoids.py | 2 +- src/sage/groups/perm_gps/permgroup_named.py | 5 +++++ src/sage/libs/gap/element.pyx | 10 +++++----- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/sage/categories/finite_groups.py b/src/sage/categories/finite_groups.py index 9aa5e0e5fd5..a0460d41399 100644 --- a/src/sage/categories/finite_groups.py +++ b/src/sage/categories/finite_groups.py @@ -58,7 +58,7 @@ def semigroup_generators(self): sage: A = AlternatingGroup(4) sage: A.semigroup_generators() - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) """ return self.group_generators() @@ -74,7 +74,7 @@ def monoid_generators(self): sage: A = AlternatingGroup(4) sage: A.monoid_generators() - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) """ return self.group_generators() @@ -116,7 +116,7 @@ def some_elements(self): sage: A = AlternatingGroup(4) sage: A.some_elements() - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) """ return self.group_generators() diff --git a/src/sage/categories/groups.py b/src/sage/categories/groups.py index 445ba111c09..22f02ae4b30 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -102,7 +102,7 @@ def group_generators(self): sage: A = AlternatingGroup(4) # needs sage.groups sage: A.group_generators() # needs sage.groups - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) """ from sage.sets.family import Family try: @@ -124,7 +124,7 @@ def monoid_generators(self): sage: # needs sage.groups sage: A = AlternatingGroup(4) sage: A.monoid_generators() - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) sage: F. = FreeGroup() sage: F.monoid_generators() Family (x, y, x^-1, y^-1) diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index 28474bd17b6..a4340621bbe 100644 --- a/src/sage/categories/monoids.py +++ b/src/sage/categories/monoids.py @@ -565,7 +565,7 @@ def algebra_generators(self): sage: A10 = AlternatingGroup(10) # needs sage.groups sage: GroupAlgebras(QQ).example(A10).algebra_generators() # needs sage.groups sage.modules - Family ((8,9,10), (1,2,3,4,5,6,7,8,9)) + Family ((1,2,3,4,5,6,7,8,9), (8,9,10)) sage: A = DihedralGroup(3).algebra(QQ); A # needs sage.groups sage.modules Algebra of Dihedral group of order 6 as a permutation group diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 222ee36f6da..f9720dcf650 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -706,8 +706,13 @@ def __init__(self, domain=None): sage: groups.permutation.Alternating(6) Alternating group of order 6!/2 as a permutation group + sage: A = AlternatingGroup(6) + sage: h = A.hom(codomain=A, im_gens=A.gens()) + sage: all(h(a) == a for a in A.gens()) + True """ PermutationGroup_symalt.__init__(self, gap_group='AlternatingGroup(%s)' % len(domain), domain=domain) + self._gens = tuple(self(g) for g in self.gap().GeneratorsOfGroup()) def _repr_(self): """ diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index bda40784a2c..334ab64149d 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1341,9 +1341,10 @@ cdef class GapElement(RingElement): sage: p.sage().parent() Fraction Field of Univariate Polynomial Ring in x over Integer Ring - sage: from sage.groups.free_group import FreeGroup - sage: G = libgap(FreeGroup(2) / [(1, 2, 2, 1)]) - sage: G == G.sage() + sage: G0 = libgap.SymplecticGroup(4,2) + sage: P = G0.IsomorphismFpGroup().Range() + sage: G = P.sage() + sage: G.gap() == P True TESTS: @@ -1351,7 +1352,6 @@ cdef class GapElement(RingElement): Check :trac:`30496`:: sage: x = libgap.Integers.Indeterminate("x") - sage: p = x^2 - 2*x sage: p.sage() x^2 - 2*x @@ -2451,7 +2451,7 @@ cdef class GapElement_Function(GapElement): [Group(()), Sym( [ 1 .. 4 ] ), Alt( [ 1 .. 4 ] ), - Group([ (1,4)(2,3), (1,2)(3,4) ])] + Group([ (1,4)(2,3), (1,3)(2,4) ])] sage: libgap.eval("a := NormalSubgroups") From c8fc6c152dd4310cac8f7fc8da98110840b7ffae Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 31 Jan 2024 10:58:55 +0100 Subject: [PATCH 24/51] private --- src/sage/groups/free_group.py | 64 +++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index a6b35a60298..944ec5bc3f7 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -699,40 +699,52 @@ class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): """ Element = FreeGroupElement - def __init__(self, generator_names, libgap_free_group=None): - """ - Python constructor. - - INPUT: - - - ``generator_names`` -- a tuple of strings. The names of the - generators. - - - ``libgap_free_group`` -- a LibGAP free group or ``None`` - (default). The LibGAP free group to wrap. If ``None``, a - suitable group will be constructed. - - TESTS:: - - sage: G. = FreeGroup() # indirect doctest - sage: G - Free Group on generators {a, b} - sage: G.variable_names() - ('a', 'b') - """ - self._assign_names(generator_names) + @staticmethod + def __classcall_private__(cls, generator_names, libgap_free_group=None): + G = super().__classcall__(cls, generator_names) + G._assign_names(generator_names) if libgap_free_group is None: libgap_free_group = libgap.FreeGroup(generator_names) - ParentLibGAP.__init__(self, libgap_free_group) + G._libgap = libgap_free_group + ParentLibGAP.__init__(G, libgap_free_group) if not generator_names: cat = Groups().Finite() else: cat = Groups().Infinite() + Group.__init__(G, category=cat) + return G + + # def __init__(self, generator_names): + # """ + # Python constructor. + # + # INPUT: + # + # - ``generator_names`` -- a tuple of strings. The names of the + # generators. + # + # - ``libgap_free_group`` -- a LibGAP free group or ``None`` + # (default). The LibGAP free group to wrap. If ``None``, a + # suitable group will be constructed. + # + # TESTS:: + # + # sage: G. = FreeGroup() # indirect doctest + # sage: G + # Free Group on generators {a, b} + # sage: G.variable_names() + # ('a', 'b') + # """ + # self._assign_names(generator_names) + # if not generator_names: + # cat = Groups().Finite() + # else: + # cat = Groups().Infinite() Group.__init__(self, category=cat) - def __reduce__(self): - from sage.structure.unique_representation import unreduce - return (unreduce, (self.__class__.__base__, (self._names, ), {})) + # def __reduce__(self): + # from sage.structure.unique_representation import unreduce + # return (unreduce, (self.__class__.__base__, (self._names, ), {})) def _repr_(self): """ From 47a7f30b150939398026fef5fba997ed2d73efa6 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 31 Jan 2024 11:01:46 +0100 Subject: [PATCH 25/51] . --- src/sage/groups/free_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 944ec5bc3f7..3ba12609ecd 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -714,7 +714,7 @@ def __classcall_private__(cls, generator_names, libgap_free_group=None): Group.__init__(G, category=cat) return G - # def __init__(self, generator_names): + def __init__(self, generator_names): # """ # Python constructor. # From eef69e347e796582c7190985d610adc33f513198 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 31 Jan 2024 13:27:49 +0100 Subject: [PATCH 26/51] . --- src/sage/groups/free_group.py | 64 ++++++++++++++++------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 3ba12609ecd..d725f254bd4 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -700,46 +700,40 @@ class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): Element = FreeGroupElement @staticmethod - def __classcall_private__(cls, generator_names, libgap_free_group=None): - G = super().__classcall__(cls, generator_names) - G._assign_names(generator_names) + def __classcall_private__(cls, data, libgap_free_group=None): + G = super().__classcall__(cls, data) if libgap_free_group is None: - libgap_free_group = libgap.FreeGroup(generator_names) - G._libgap = libgap_free_group - ParentLibGAP.__init__(G, libgap_free_group) + libgap_free_group = libgap.FreeGroup(data) + G._gap = libgap_free_group + return G + + def __init__(self, generator_names): + """ + Python constructor. + + INPUT: + + - ``generator_names`` -- a tuple of strings. The names of the + generators. + + - ``libgap_free_group`` -- a LibGAP free group or ``None`` + (default). The LibGAP free group to wrap. If ``None``, a + suitable group will be constructed. + + TESTS:: + + sage: G. = FreeGroup() # indirect doctest + sage: G + Free Group on generators {a, b} + sage: G.variable_names() + ('a', 'b') + """ + self._assign_names(generator_names) + ParentLibGAP.__init__(self, self._gap) if not generator_names: cat = Groups().Finite() else: cat = Groups().Infinite() - Group.__init__(G, category=cat) - return G - - def __init__(self, generator_names): - # """ - # Python constructor. - # - # INPUT: - # - # - ``generator_names`` -- a tuple of strings. The names of the - # generators. - # - # - ``libgap_free_group`` -- a LibGAP free group or ``None`` - # (default). The LibGAP free group to wrap. If ``None``, a - # suitable group will be constructed. - # - # TESTS:: - # - # sage: G. = FreeGroup() # indirect doctest - # sage: G - # Free Group on generators {a, b} - # sage: G.variable_names() - # ('a', 'b') - # """ - # self._assign_names(generator_names) - # if not generator_names: - # cat = Groups().Finite() - # else: - # cat = Groups().Infinite() Group.__init__(self, category=cat) # def __reduce__(self): From d68a8b21a8992271ba1b0dd1a7cb9937117c65e3 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 31 Jan 2024 15:33:19 +0100 Subject: [PATCH 27/51] CachedRepresentation --- src/sage/groups/finitely_presented.py | 4 ++-- src/sage/groups/free_group.py | 24 +++++++++--------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 0ebccdd0b0c..e7794c2068c 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -143,7 +143,7 @@ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ from sage.sets.set import Set -from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.unique_representation import CachedRepresentation class GroupMorphismWithGensImages(SetMorphism): @@ -743,7 +743,7 @@ def make_confluent(self): raise ValueError('could not make the system confluent') -class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, Group, ParentLibGAP): +class FinitelyPresentedGroup(GroupMixinLibGAP, CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's Finitely Presented Groups. diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index d725f254bd4..0b7c7d1c14b 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -63,7 +63,7 @@ from sage.categories.groups import Groups from sage.groups.group import Group from sage.groups.libgap_wrapper import ParentLibGAP, ElementLibGAP -from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.unique_representation import CachedRepresentation from sage.libs.gap.libgap import libgap from sage.libs.gap.element import GapElement from sage.rings.integer import Integer @@ -684,7 +684,7 @@ def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds): return FreeGroup_class(names) -class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): +class FreeGroup_class(CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's FreeGroup @@ -699,15 +699,7 @@ class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): """ Element = FreeGroupElement - @staticmethod - def __classcall_private__(cls, data, libgap_free_group=None): - G = super().__classcall__(cls, data) - if libgap_free_group is None: - libgap_free_group = libgap.FreeGroup(data) - G._gap = libgap_free_group - return G - - def __init__(self, generator_names): + def __init__(self, generator_names, libgap_free_group=None): """ Python constructor. @@ -729,16 +721,18 @@ def __init__(self, generator_names): ('a', 'b') """ self._assign_names(generator_names) - ParentLibGAP.__init__(self, self._gap) + if libgap_free_group is None: + libgap_free_group = libgap.FreeGroup(generator_names) + ParentLibGAP.__init__(self, libgap_free_group) if not generator_names: cat = Groups().Finite() else: cat = Groups().Infinite() Group.__init__(self, category=cat) - # def __reduce__(self): - # from sage.structure.unique_representation import unreduce - # return (unreduce, (self.__class__.__base__, (self._names, ), {})) + def __reduce__(self): + from sage.structure.unique_representation import unreduce + return (unreduce, (self.__class__.__base__, (self._names, ), {})) def _repr_(self): """ From 32dac97478c2190d97368e04851c37389b95b9bd Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 31 Jan 2024 17:44:12 +0100 Subject: [PATCH 28/51] hash --- src/sage/groups/finitely_presented.py | 3 +++ src/sage/groups/free_group.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index e7794c2068c..0392964d19f 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -828,6 +828,9 @@ def __reduce__(self): from sage.structure.unique_representation import unreduce return (unreduce, (self.__class__.__base__, (self._free_group, self._relations), {})) + def __hash__(self): + return hash((self._free_group, self._relations, self._names)) + def _repr_(self): """ Return a string representation. diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 0b7c7d1c14b..819eb5a746d 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -730,6 +730,9 @@ def __init__(self, generator_names, libgap_free_group=None): cat = Groups().Infinite() Group.__init__(self, category=cat) + def __hash__(self): + return hash((self.__class__, self._names)) + def __reduce__(self): from sage.structure.unique_representation import unreduce return (unreduce, (self.__class__.__base__, (self._names, ), {})) From 81f63a1229168bf2a8d01d600f5e079f7d7ef138 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 31 Jan 2024 23:56:16 +0100 Subject: [PATCH 29/51] doctests --- src/sage/groups/artin.py | 14 ++++++ src/sage/groups/cubic_braid.py | 10 ++++ src/sage/groups/finitely_presented.py | 68 +++++---------------------- src/sage/groups/free_group.py | 21 +++++++++ src/sage/groups/raag.py | 9 ++++ src/sage/libs/gap/element.pyx | 10 ++-- 6 files changed, 72 insertions(+), 60 deletions(-) diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index 78ee82f881a..ecb7a6e4a25 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -491,6 +491,20 @@ def __init__(self, coxeter_matrix, names): FinitelyPresentedGroup.__init__(self, free_group, tuple(rels)) def __reduce__(self): + """ + Implement pickling. + + TESTS:: + + sage: A = ArtinGroup(['B',3], ['x','y','z']) + sage: A.__reduce__()[1] + (, + ( + [1 3 2] + [3 1 4] + [2 4 1], ('x', 'y', 'z') + ), {}) + """ from sage.structure.unique_representation import unreduce return (unreduce, (self.__class__.__base__, (self._coxeter_matrix, self._names), {})) diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index 52707a2e0c5..75243cdfd35 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -806,6 +806,16 @@ def __init__(self, names, cbg_type=None): return def __reduce__(self): + """ + Implement pickling. + + TESTS:: + + sage: CubicBraidGroup(3).__reduce__()[1] + (, + (('c0', 'c1'),), + {'cbg_type': None}) + """ from sage.structure.unique_representation import unreduce return (unreduce, (self.__class__.__base__, (self._names,), {'cbg_type': self._cbg_type_orig})) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 0392964d19f..5f6213c3250 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -130,7 +130,6 @@ from sage.arith.misc import GCD as gcd from sage.categories.morphism import SetMorphism -from sage.functions.generalized import sign from sage.groups.free_group import FreeGroup from sage.groups.free_group import FreeGroupElement from sage.groups.group import Group @@ -370,55 +369,6 @@ def __call__(self, *values, **kwds): return super().__call__(values) -# def wrap_FpGroup(libgap_fpgroup): -# """ -# Wrap a GAP finitely presented group. -# -# This function changes the comparison method of -# ``libgap_free_group`` to comparison by Python ``id``. If you want -# to put the LibGAP free group into a container ``(set, dict)`` then you -# should understand the implications of -# :meth:`~sage.libs.gap.element.GapElement._set_compare_by_id`. To -# be safe, it is recommended that you just work with the resulting -# Sage :class:`FinitelyPresentedGroup`. -# -# INPUT: -# -# - ``libgap_fpgroup`` -- a LibGAP finitely presented group -# -# OUTPUT: -# -# A Sage :class:`FinitelyPresentedGroup`. -# -# EXAMPLES: -# -# First construct a LibGAP finitely presented group:: -# -# sage: F = libgap.FreeGroup(['a', 'b']) -# sage: a_cubed = F.GeneratorsOfGroup()[0] ^ 3 -# sage: P = F / libgap([ a_cubed ]); P -# -# sage: type(P) -# -# -# Now wrap it:: -# -# sage: from sage.groups.finitely_presented import wrap_FpGroup -# sage: wrap_FpGroup(P) -# Finitely presented group < a, b | a^3 > -# """ -# assert libgap_fpgroup.IsFpGroup() -# libgap_fpgroup._set_compare_by_id() -# # from sage.groups.free_group import wrap_FreeGroup -# # free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup()) -# names = tuple(str(g) for g in libgap_fpgroup.FreeGroupOfFpGroup().GeneratorsOfGroup()) -# F = FreeGroup(names) -# relations = tuple(F(rel.UnderlyingElement().LetterRepAssocWord().sage()) -# for rel in libgap_fpgroup.RelatorsOfFpGroup()) -# G = FinitelyPresentedGroup(F, relations) -# return G - - class RewritingSystem(): """ A class that wraps GAP's rewriting systems. @@ -819,16 +769,24 @@ def __reduce__(self): TESTS:: - sage: F. = FreeGroup() - sage: a.__reduce__() - (Free Group on generators {a, b}, ((1,),)) - sage: (a*b*a^-1).__reduce__() - (Free Group on generators {a, b}, ((1, 2, -1),)) + sage: G = FreeGroup(2) / [(1, 2, 2, 1)] + sage: G.__reduce__()[1] + (, + (Free Group on generators {x0, x1}, (x0*x1^2*x0,)), {}) """ from sage.structure.unique_representation import unreduce return (unreduce, (self.__class__.__base__, (self._free_group, self._relations), {})) def __hash__(self): + """ + Make hashable. + + EXAMPLES:: + + sage: G = FreeGroup(2) / [(1, 2, 2, 1)] + sage: G.__hash__() # random output + -468022353355363043 + """ return hash((self._free_group, self._relations, self._names)) def _repr_(self): diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 819eb5a746d..d7ae703816e 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -731,9 +731,30 @@ def __init__(self, generator_names, libgap_free_group=None): Group.__init__(self, category=cat) def __hash__(self): + """ + Make hashable. + + EXAMPLES:: + + sage: F = FreeGroup(3) + sage: F.__hash__() # random output + -2230941415428039205 + """ + return hash((self.__class__, self._names)) def __reduce__(self): + """ + Implement pickling. + + TESTS:: + + sage: F. = FreeGroup() + sage: F.__reduce__()[1] + (, + (('a', 'b'),), {}) + """ + from sage.structure.unique_representation import unreduce return (unreduce, (self.__class__.__base__, (self._names, ), {})) diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index 3e5091baf1b..a5596d20882 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -206,6 +206,15 @@ def __init__(self, G, names): category=Groups().Infinite()) def __reduce__(self): + """ + Implement pickling. + + TESTS:: + sage: RightAngledArtinGroup(graphs.CycleGraph(5)).__reduce__()[1] + (, + (Cycle graph: Graph on 5 vertices, ('v0', 'v1', 'v2', 'v3', 'v4')), + {}) + """ from sage.structure.unique_representation import unreduce return (unreduce, (self.__class__.__base__, (self._graph, self._names), {})) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 334ab64149d..1c0352f6ff1 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1394,6 +1394,11 @@ cdef class GapElement(RingElement): x = R.gen() return x**val * R(num) / R(den) + elif self.IsList(): + # May be a list-like collection of some other type of GapElements + # that we can convert + return [item.sage() for item in self.AsList()] + elif self.IsFpGroup(): from sage.groups.free_group import FreeGroup from sage.groups.finitely_presented import FinitelyPresentedGroup @@ -1404,11 +1409,6 @@ cdef class GapElement(RingElement): for rel in self.RelatorsOfFpGroup()) return FinitelyPresentedGroup(F, relations, libgap_fpgroup=self) - elif self.IsList(): - # May be a list-like collection of some other type of GapElements - # that we can convert - return [item.sage() for item in self.AsList()] - raise NotImplementedError('cannot construct equivalent Sage object') From d87dffc710cfe14e5149ae6891af8a174e136137 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Thu, 1 Feb 2024 07:30:39 +0100 Subject: [PATCH 30/51] change test in element.pyx --- src/sage/libs/gap/element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 1c0352f6ff1..f0632d37680 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -2451,7 +2451,7 @@ cdef class GapElement_Function(GapElement): [Group(()), Sym( [ 1 .. 4 ] ), Alt( [ 1 .. 4 ] ), - Group([ (1,4)(2,3), (1,3)(2,4) ])] + Group([ (1,4)(2,3), (1,2)(3,4) ])] sage: libgap.eval("a := NormalSubgroups") From 07517c8edd0be5d268c60ee1d740bfa99d280e29 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Thu, 1 Feb 2024 07:31:22 +0100 Subject: [PATCH 31/51] change test in element.pyx --- src/sage/libs/gap/element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 334ab64149d..c6cb48d7adf 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -2451,7 +2451,7 @@ cdef class GapElement_Function(GapElement): [Group(()), Sym( [ 1 .. 4 ] ), Alt( [ 1 .. 4 ] ), - Group([ (1,4)(2,3), (1,3)(2,4) ])] + Group([ (1,4)(2,3), (1,2)(3,4) ])] sage: libgap.eval("a := NormalSubgroups") From 58c2bc2e791880a034da2b271dec52d8d4d6b233 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Thu, 1 Feb 2024 07:35:59 +0100 Subject: [PATCH 32/51] add blank line --- src/sage/groups/raag.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index a5596d20882..8ffb727af13 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -210,6 +210,7 @@ def __reduce__(self): Implement pickling. TESTS:: + sage: RightAngledArtinGroup(graphs.CycleGraph(5)).__reduce__()[1] (, (Cycle graph: Graph on 5 vertices, ('v0', 'v1', 'v2', 'v3', 'v4')), From 7eeba06978d6eaf3a8e06412563678b427c08012 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Fri, 2 Feb 2024 16:36:06 +0100 Subject: [PATCH 33/51] first attempt for richcmp --- src/sage/groups/finitely_presented.py | 22 ++++++++++++++++++++++ src/sage/groups/free_group.py | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 5f6213c3250..144920f6a01 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -142,6 +142,7 @@ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ from sage.sets.set import Set +from sage.structure.richcmp import richcmp from sage.structure.unique_representation import CachedRepresentation @@ -789,6 +790,27 @@ def __hash__(self): """ return hash((self._free_group, self._relations, self._names)) + def _richcmp_(self, other, op): + """ + Compare ``self`` and ``other``. + + TESTS:: + + sage: F1 = FreeGroup(2) / [(1, 2, 2, 1)] + sage: F2 = FreeGroup(2, 'x') / [(1, 2, 2, 1)] + sage: F3 = FreeGroup(2) / [(1, 1, 2, 2)] + sage: F4 = FreeGroup(2, 'y') / [(1, 2, 2, 1)] + sage: F1 == F2 + True + sage: F2 == F3 + False + sage: F3 == F4 + False + """ + r1 = [r.Tietze() for r in self._relations] + r2 = [r.Tietze() for r in other._relations] + return richcmp((self._names, r1), (other._names, r2), op) + def _repr_(self): """ Return a string representation. diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index d7ae703816e..c84779c8d7f 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -72,6 +72,7 @@ from sage.misc.misc_c import prod from sage.structure.sequence import Sequence from sage.structure.element import coercion_model, parent +from sage.structure.richcmp import richcmp def is_FreeGroup(x): @@ -743,6 +744,25 @@ def __hash__(self): return hash((self.__class__, self._names)) + def _richcmp_(self, other, op): + """ + Compare ``self`` and ``other``. + + TESTS:: + + sage: F1 = FreeGroup(2) + sage: F2 = FreeGroup(2, 'x') + sage: F3 = FreeGroup('x0, x1') + sage: F4 = FreeGroup(2, 'y') + sage: F1 == F2 + True + sage: F2 == F3 + True + sage: F3 == F4 + False + """ + return richcmp(self._names, other._names, op) + def __reduce__(self): """ Implement pickling. From bb26a7bc858c040c97d6c117dc15c7ff0e7562fc Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Tue, 6 Feb 2024 12:38:26 +0100 Subject: [PATCH 34/51] sage method for free groups and first attempt to richcmp_method --- src/sage/groups/finitely_presented.py | 5 +++-- src/sage/groups/free_group.py | 7 +++++-- src/sage/libs/gap/element.pyx | 11 +++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 144920f6a01..65ea361ba30 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -142,7 +142,7 @@ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ from sage.sets.set import Set -from sage.structure.richcmp import richcmp +from sage.structure.richcmp import richcmp, richcmp_method from sage.structure.unique_representation import CachedRepresentation @@ -694,6 +694,7 @@ def make_confluent(self): raise ValueError('could not make the system confluent') +@richcmp_method class FinitelyPresentedGroup(GroupMixinLibGAP, CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's Finitely Presented Groups. @@ -790,7 +791,7 @@ def __hash__(self): """ return hash((self._free_group, self._relations, self._names)) - def _richcmp_(self, other, op): + def __richcmp__(self, other, op): """ Compare ``self`` and ``other``. diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index c84779c8d7f..a6febf2ab7c 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -72,7 +72,7 @@ from sage.misc.misc_c import prod from sage.structure.sequence import Sequence from sage.structure.element import coercion_model, parent -from sage.structure.richcmp import richcmp +from sage.structure.richcmp import richcmp, richcmp_method def is_FreeGroup(x): @@ -685,6 +685,7 @@ def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds): return FreeGroup_class(names) +@richcmp_method class FreeGroup_class(CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's FreeGroup @@ -744,7 +745,7 @@ def __hash__(self): return hash((self.__class__, self._names)) - def _richcmp_(self, other, op): + def __richcmp__(self, other, op): """ Compare ``self`` and ``other``. @@ -761,6 +762,8 @@ def _richcmp_(self, other, op): sage: F3 == F4 False """ + if not isinstance(other, self.__class__): + return False return richcmp(self._names, other._names, op) def __reduce__(self): diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index f0632d37680..ada238a6db9 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1347,6 +1347,11 @@ cdef class GapElement(RingElement): sage: G.gap() == P True + sage: F0 = libgap.FreeGroup(2) + sage: F = F0.sage() + sage: F.gap() is F + True + TESTS: Check :trac:`30496`:: @@ -1399,6 +1404,12 @@ cdef class GapElement(RingElement): # that we can convert return [item.sage() for item in self.AsList()] + elif self.IsFreeGroup(): + from sage.groups.free_group import FreeGroup_class + self._set_compare_by_id() + names = tuple(str(g) for g in self.GeneratorsOfGroup()) + return FreeGroup_class(names, libgap_free_group=self) + elif self.IsFpGroup(): from sage.groups.free_group import FreeGroup from sage.groups.finitely_presented import FinitelyPresentedGroup From 8d05aa1c8a59c00211e72353555eeac00469a414 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Tue, 6 Feb 2024 15:20:28 +0100 Subject: [PATCH 35/51] change reduce --- src/sage/groups/artin.py | 39 +++++++++++++++++----------------- src/sage/groups/cubic_braid.py | 17 ++------------- src/sage/groups/raag.py | 16 ++------------ 3 files changed, 24 insertions(+), 48 deletions(-) diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index ecb7a6e4a25..0c847a6baa4 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -28,6 +28,7 @@ from sage.combinat.root_system.coxeter_group import CoxeterGroup from sage.rings.infinity import Infinity from sage.structure.richcmp import richcmp, rich_to_bool +from sage.structure.unique_representation import CachedRepresentation class ArtinGroupElement(FinitelyPresentedGroupElement): @@ -472,8 +473,6 @@ def __init__(self, coxeter_matrix, names): sage: A = ArtinGroup(['B',3], ['x','y','z']) sage: TestSuite(A).run() """ - self._names = names - self._coxeter_matrix = coxeter_matrix self._coxeter_group = CoxeterGroup(self._coxeter_matrix) free_group = FreeGroup(names) rels = [] @@ -490,23 +489,25 @@ def __init__(self, coxeter_matrix, names): rels.append(free_group(elt)) FinitelyPresentedGroup.__init__(self, free_group, tuple(rels)) - def __reduce__(self): - """ - Implement pickling. - - TESTS:: - - sage: A = ArtinGroup(['B',3], ['x','y','z']) - sage: A.__reduce__()[1] - (, - ( - [1 3 2] - [3 1 4] - [2 4 1], ('x', 'y', 'z') - ), {}) - """ - from sage.structure.unique_representation import unreduce - return (unreduce, (self.__class__.__base__, (self._coxeter_matrix, self._names), {})) + __reduce__ = CachedRepresentation.__reduce__ + + # def __reduce__(self): + # """ + # Implement pickling. + # + # TESTS:: + # + # sage: A = ArtinGroup(['B',3], ['x','y','z']) + # sage: A.__reduce__()[1] + # (, + # ( + # [1 3 2] + # [3 1 4] + # [2 4 1], ('x', 'y', 'z') + # ), {}) + # """ + # from sage.structure.unique_representation import unreduce + # return (unreduce, (self.__class__.__base__, (self._coxeter_matrix, self._names), {})) def _repr_(self): """ diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index 75243cdfd35..fefe316a0e1 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -91,6 +91,7 @@ from sage.groups.braid import BraidGroup from sage.misc.cachefunc import cached_method from sage.rings.integer import Integer +from sage.structure.unique_representation import CachedRepresentation try: from sage.libs.gap.element import GapElement @@ -750,7 +751,6 @@ def __init__(self, names, cbg_type=None): n = Integer(len(names)) if n < 1: raise ValueError("the number of strands must be an integer larger than one") - self._cbg_type_orig = cbg_type if cbg_type is None: cbg_type = CubicBraidGroup.type.Coxeter if not isinstance(cbg_type, CubicBraidGroup.type): @@ -791,7 +791,6 @@ def __init__(self, names, cbg_type=None): cat = Groups().Infinite() FinitelyPresentedGroup.__init__(self, free_group, tuple(rels), category=cat) self._free_group = free_group - self._names = names # ------------------------------------------------------------------------------------------------ # the following global pointers to classical group realizations will be set in the private method @@ -805,19 +804,7 @@ def __init__(self, names, cbg_type=None): self._centralizing_element = None # image under nat. map of the former one in the proj. classical group return - def __reduce__(self): - """ - Implement pickling. - - TESTS:: - - sage: CubicBraidGroup(3).__reduce__()[1] - (, - (('c0', 'c1'),), - {'cbg_type': None}) - """ - from sage.structure.unique_representation import unreduce - return (unreduce, (self.__class__.__base__, (self._names,), {'cbg_type': self._cbg_type_orig})) + __reduce__ = CachedRepresentation.__reduce__ def _repr_(self): r""" diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index 8ffb727af13..17cfbfbea62 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -38,6 +38,7 @@ from sage.categories.groups import Groups from sage.categories.algebras_with_basis import AlgebrasWithBasis from sage.algebras.clifford_algebra_element import CohomologyRAAGElement +from sage.structure.unique_representation import CachedRepresentation from sage.typeset.ascii_art import ascii_art from sage.typeset.unicode_art import unicode_art @@ -189,7 +190,6 @@ def __init__(self, G, names): Category of infinite groups """ self._graph = G - self._names = names F = FreeGroup(names=names) CG = Graph(G).complement() # Make sure it's mutable CG.relabel() # Standardize the labels @@ -205,19 +205,7 @@ def __init__(self, G, names): FinitelyPresentedGroup.__init__(self, F, rels, category=Groups().Infinite()) - def __reduce__(self): - """ - Implement pickling. - - TESTS:: - - sage: RightAngledArtinGroup(graphs.CycleGraph(5)).__reduce__()[1] - (, - (Cycle graph: Graph on 5 vertices, ('v0', 'v1', 'v2', 'v3', 'v4')), - {}) - """ - from sage.structure.unique_representation import unreduce - return (unreduce, (self.__class__.__base__, (self._graph, self._names), {})) + __reduce__ = CachedRepresentation.__reduce__ def _repr_(self) -> str: """ From 336865be9f9c2ff0561fdf3d765b61d25833df09 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Tue, 6 Feb 2024 16:07:32 +0100 Subject: [PATCH 36/51] forget richcmp --- src/sage/groups/artin.py | 2 +- src/sage/groups/finitely_presented.py | 44 +++++++++++++-------------- src/sage/groups/free_group.py | 44 +++++++++++++-------------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index 0c847a6baa4..79c9730ec6c 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -473,7 +473,7 @@ def __init__(self, coxeter_matrix, names): sage: A = ArtinGroup(['B',3], ['x','y','z']) sage: TestSuite(A).run() """ - self._coxeter_group = CoxeterGroup(self._coxeter_matrix) + self._coxeter_group = CoxeterGroup(coxeter_matrix) free_group = FreeGroup(names) rels = [] # Generate the relations based on the Coxeter graph diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 65ea361ba30..2907376cdb3 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -142,7 +142,7 @@ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ from sage.sets.set import Set -from sage.structure.richcmp import richcmp, richcmp_method +# from sage.structure.richcmp import richcmp from sage.structure.unique_representation import CachedRepresentation @@ -694,7 +694,7 @@ def make_confluent(self): raise ValueError('could not make the system confluent') -@richcmp_method +# @richcmp_method class FinitelyPresentedGroup(GroupMixinLibGAP, CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's Finitely Presented Groups. @@ -791,26 +791,26 @@ def __hash__(self): """ return hash((self._free_group, self._relations, self._names)) - def __richcmp__(self, other, op): - """ - Compare ``self`` and ``other``. - - TESTS:: - - sage: F1 = FreeGroup(2) / [(1, 2, 2, 1)] - sage: F2 = FreeGroup(2, 'x') / [(1, 2, 2, 1)] - sage: F3 = FreeGroup(2) / [(1, 1, 2, 2)] - sage: F4 = FreeGroup(2, 'y') / [(1, 2, 2, 1)] - sage: F1 == F2 - True - sage: F2 == F3 - False - sage: F3 == F4 - False - """ - r1 = [r.Tietze() for r in self._relations] - r2 = [r.Tietze() for r in other._relations] - return richcmp((self._names, r1), (other._names, r2), op) + # def __richcmp__(self, other, op): + # """ + # Compare ``self`` and ``other``. + # + # TESTS:: + # + # sage: F1 = FreeGroup(2) / [(1, 2, 2, 1)] + # sage: F2 = FreeGroup(2, 'x') / [(1, 2, 2, 1)] + # sage: F3 = FreeGroup(2) / [(1, 1, 2, 2)] + # sage: F4 = FreeGroup(2, 'y') / [(1, 2, 2, 1)] + # sage: F1 == F2 + # True + # sage: F2 == F3 + # False + # sage: F3 == F4 + # False + # """ + # r1 = [r.Tietze() for r in self._relations] + # r2 = [r.Tietze() for r in other._relations] + # return richcmp((self._names, r1), (other._names, r2), op) def _repr_(self): """ diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index a6febf2ab7c..61303fccded 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -72,7 +72,7 @@ from sage.misc.misc_c import prod from sage.structure.sequence import Sequence from sage.structure.element import coercion_model, parent -from sage.structure.richcmp import richcmp, richcmp_method +# from sage.structure.richcmp import richcmp, richcmp_method def is_FreeGroup(x): @@ -685,7 +685,7 @@ def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds): return FreeGroup_class(names) -@richcmp_method +# @richcmp_method class FreeGroup_class(CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's FreeGroup @@ -745,26 +745,26 @@ def __hash__(self): return hash((self.__class__, self._names)) - def __richcmp__(self, other, op): - """ - Compare ``self`` and ``other``. - - TESTS:: - - sage: F1 = FreeGroup(2) - sage: F2 = FreeGroup(2, 'x') - sage: F3 = FreeGroup('x0, x1') - sage: F4 = FreeGroup(2, 'y') - sage: F1 == F2 - True - sage: F2 == F3 - True - sage: F3 == F4 - False - """ - if not isinstance(other, self.__class__): - return False - return richcmp(self._names, other._names, op) + # def __richcmp__(self, other, op): + # """ + # Compare ``self`` and ``other``. + # + # TESTS:: + # + # sage: F1 = FreeGroup(2) + # sage: F2 = FreeGroup(2, 'x') + # sage: F3 = FreeGroup('x0, x1') + # sage: F4 = FreeGroup(2, 'y') + # sage: F1 == F2 + # True + # sage: F2 == F3 + # True + # sage: F3 == F4 + # False + # """ + # if not isinstance(other, self.__class__): + # return False + # return richcmp(self._names, other._names, op) def __reduce__(self): """ From f9cd366aa527787ab96f1bae22f5eb2b31bc33e8 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Tue, 6 Feb 2024 16:14:56 +0100 Subject: [PATCH 37/51] traling spaces --- src/sage/groups/free_group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 61303fccded..e3afe728f98 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -748,9 +748,9 @@ def __hash__(self): # def __richcmp__(self, other, op): # """ # Compare ``self`` and ``other``. - # + # # TESTS:: - # + # # sage: F1 = FreeGroup(2) # sage: F2 = FreeGroup(2, 'x') # sage: F3 = FreeGroup('x0, x1') From 4f4eb8efb11599903438e143cdeda824ffa73c4e Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Tue, 6 Feb 2024 16:21:09 +0100 Subject: [PATCH 38/51] delete commented code --- src/sage/groups/free_group.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index e3afe728f98..52ff4df897e 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -745,27 +745,6 @@ def __hash__(self): return hash((self.__class__, self._names)) - # def __richcmp__(self, other, op): - # """ - # Compare ``self`` and ``other``. - # - # TESTS:: - # - # sage: F1 = FreeGroup(2) - # sage: F2 = FreeGroup(2, 'x') - # sage: F3 = FreeGroup('x0, x1') - # sage: F4 = FreeGroup(2, 'y') - # sage: F1 == F2 - # True - # sage: F2 == F3 - # True - # sage: F3 == F4 - # False - # """ - # if not isinstance(other, self.__class__): - # return False - # return richcmp(self._names, other._names, op) - def __reduce__(self): """ Implement pickling. From 4599efb1d2d5cc2cd42b768a9902c9eaf1b13c68 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Tue, 6 Feb 2024 16:25:36 +0100 Subject: [PATCH 39/51] warning in doc --- src/sage/groups/finitely_presented.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 2907376cdb3..75278dd404f 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -704,7 +704,9 @@ class FinitelyPresentedGroup(GroupMixinLibGAP, CachedRepresentation, Group, Pare You should use :meth:`~sage.groups.free_group.FreeGroup_class.quotient` to construct finitely presented groups as quotients of free - groups. + groups. Any class inheriting this one should define + ``__reduce__ = CachedRepresentation.__reduce__`` + after importing ``CachedRepresentation``. EXAMPLES:: From a83319e83d59310f397caddf567cba4507b22dae Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Tue, 6 Feb 2024 16:41:47 +0100 Subject: [PATCH 40/51] delete more commented code --- src/sage/groups/finitely_presented.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 75278dd404f..677a365c141 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -793,27 +793,6 @@ def __hash__(self): """ return hash((self._free_group, self._relations, self._names)) - # def __richcmp__(self, other, op): - # """ - # Compare ``self`` and ``other``. - # - # TESTS:: - # - # sage: F1 = FreeGroup(2) / [(1, 2, 2, 1)] - # sage: F2 = FreeGroup(2, 'x') / [(1, 2, 2, 1)] - # sage: F3 = FreeGroup(2) / [(1, 1, 2, 2)] - # sage: F4 = FreeGroup(2, 'y') / [(1, 2, 2, 1)] - # sage: F1 == F2 - # True - # sage: F2 == F3 - # False - # sage: F3 == F4 - # False - # """ - # r1 = [r.Tietze() for r in self._relations] - # r2 = [r.Tietze() for r in other._relations] - # return richcmp((self._names, r1), (other._names, r2), op) - def _repr_(self): """ Return a string representation. From edffae313cd3da3d065486fca53eb34c2fbc6459 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 11 Mar 2024 19:12:22 +0100 Subject: [PATCH 41/51] -Change in unique representation to get a better mro -Changes in free_group and finitely_presented to improve pickle -Revert changes in artin, to do in raag and cubic_braid -Changes in tests caused by the previous ones, still problems with cartan_type --- src/sage/categories/category_singleton.pyx | 4 +- src/sage/categories/sets_cat.py | 4 +- src/sage/groups/artin.py | 24 +------- src/sage/groups/finitely_presented.py | 40 +++++++------ src/sage/groups/free_group.py | 66 ++++++++++++--------- src/sage/libs/gap/element.pyx | 10 ++-- src/sage/structure/unique_representation.py | 2 +- 7 files changed, 71 insertions(+), 79 deletions(-) diff --git a/src/sage/categories/category_singleton.pyx b/src/sage/categories/category_singleton.pyx index 543ce0375f7..2d687a52f92 100644 --- a/src/sage/categories/category_singleton.pyx +++ b/src/sage/categories/category_singleton.pyx @@ -217,14 +217,14 @@ class Category_singleton(Category): , , , - , , + , , , , , , - <... 'object'>] + ] sage: R() is R() True sage: R() is R().__class__() diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 0a523f00903..41dde76f42c 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -135,15 +135,15 @@ class Sets(Category_singleton): - + - <... 'object'> + We run some generic checks on P:: diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index d38ea6b0eca..49720518919 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -28,7 +28,7 @@ from sage.combinat.root_system.coxeter_group import CoxeterGroup from sage.rings.infinity import Infinity from sage.structure.richcmp import richcmp, rich_to_bool -from sage.structure.unique_representation import CachedRepresentation +from sage.structure.unique_representation import UniqueRepresentation class ArtinGroupElement(FinitelyPresentedGroupElement): @@ -332,7 +332,7 @@ def _left_normal_form_coxeter(self): return tuple([-delta] + form) -class ArtinGroup(FinitelyPresentedGroup): +class ArtinGroup(UniqueRepresentation, FinitelyPresentedGroup): r""" An Artin group. @@ -489,26 +489,6 @@ def __init__(self, coxeter_matrix, names): rels.append(free_group(elt)) FinitelyPresentedGroup.__init__(self, free_group, tuple(rels)) - __reduce__ = CachedRepresentation.__reduce__ - - # def __reduce__(self): - # """ - # Implement pickling. - # - # TESTS:: - # - # sage: A = ArtinGroup(['B',3], ['x','y','z']) - # sage: A.__reduce__()[1] - # (, - # ( - # [1 3 2] - # [3 1 4] - # [2 4 1], ('x', 'y', 'z') - # ), {}) - # """ - # from sage.structure.unique_representation import unreduce - # return (unreduce, (self.__class__.__base__, (self._coxeter_matrix, self._names), {})) - def _repr_(self): """ Return a string representation of ``self``. diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index b255af843d1..a9d2e0d3756 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -142,7 +142,7 @@ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ from sage.sets.set import Set -# from sage.structure.richcmp import richcmp +from sage.structure.richcmp import richcmp, richcmp_method from sage.structure.unique_representation import CachedRepresentation @@ -694,7 +694,7 @@ def make_confluent(self): raise ValueError('could not make the system confluent') -# @richcmp_method +@richcmp_method class FinitelyPresentedGroup(GroupMixinLibGAP, CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's Finitely Presented Groups. @@ -761,37 +761,39 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): assert isinstance(relations, tuple) self._free_group = free_group self._relations = relations - self._assign_names(free_group.variable_names()) + try: + self._assign_names(free_group.variable_names()) + except ValueError: + pass if libgap_fpgroup is None: libgap_fpgroup = free_group.gap() / libgap([rel.gap() for rel in relations]) ParentLibGAP.__init__(self, libgap_fpgroup) Group.__init__(self, category=category) - def __reduce__(self): + def __hash__(self): """ - Implement pickling. + Make hashable. - TESTS:: + EXAMPLES:: sage: G = FreeGroup(2) / [(1, 2, 2, 1)] - sage: G.__reduce__()[1] - (, - (Free Group on generators {x0, x1}, (x0*x1^2*x0,)), {}) + sage: G.__hash__() == hash((G.free_group(), G.relations())) + True """ - from sage.structure.unique_representation import unreduce - return (unreduce, (self.__class__.__base__, (self._free_group, self._relations), {})) + return hash((self._free_group, self._relations)) - def __hash__(self): + def __richcmp__(self, other, op): """ - Make hashable. + Rich comparison of ``self`` and ``other``. EXAMPLES:: - - sage: G = FreeGroup(2) / [(1, 2, 2, 1)] - sage: G.__hash__() # random output - -468022353355363043 """ - return hash((self._free_group, self._relations, self._names)) + if not isinstance(other, self.__class__): + from sage.structure.richcmp import op_NE + return (op == op_NE) + self_data = (self._free_group, self._relations) + other_data = (other._free_group, other._relations) + return richcmp(self_data, other_data, op) def _repr_(self): """ @@ -810,7 +812,7 @@ def _repr_(self): sage: H._repr_() 'Finitely presented group < a, b | a, b^3 >' """ - gens = ', '.join(self.variable_names()) + gens = ', '.join(self._free_group._gen_names) rels = ', '.join([str(r) for r in self.relations()]) return 'Finitely presented group ' + '< ' + gens + ' | ' + rels + ' >' diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index fd6b0426a7c..66525e53bce 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -72,7 +72,7 @@ from sage.misc.misc_c import prod from sage.structure.sequence import Sequence from sage.structure.element import coercion_model, parent -# from sage.structure.richcmp import richcmp, richcmp_method +from sage.structure.richcmp import richcmp, richcmp_method def is_FreeGroup(x): @@ -682,10 +682,10 @@ def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds): from sage.groups.indexed_free_group import IndexedFreeGroup return IndexedFreeGroup(index_set, names=names, **kwds) - return FreeGroup_class(names) + return FreeGroup_class(names, **kwds) -# @richcmp_method +@richcmp_method class FreeGroup_class(CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's FreeGroup @@ -701,7 +701,7 @@ class FreeGroup_class(CachedRepresentation, Group, ParentLibGAP): """ Element = FreeGroupElement - def __init__(self, generator_names, libgap_free_group=None): + def __init__(self, generator_names, gap_group=None): """ Python constructor. @@ -722,15 +722,19 @@ def __init__(self, generator_names, libgap_free_group=None): sage: G.variable_names() ('a', 'b') """ - self._assign_names(generator_names) - if libgap_free_group is None: - libgap_free_group = libgap.FreeGroup(generator_names) - ParentLibGAP.__init__(self, libgap_free_group) + if gap_group is None: + gap_group = libgap.FreeGroup(generator_names) + ParentLibGAP.__init__(self, gap_group) if not generator_names: cat = Groups().Finite() else: cat = Groups().Infinite() Group.__init__(self, category=cat) + self._gen_names = generator_names + try: + self._assign_names(generator_names) + except ValueError: + pass def __hash__(self): """ @@ -739,26 +743,34 @@ def __hash__(self): EXAMPLES:: sage: F = FreeGroup(3) - sage: F.__hash__() # random output - -2230941415428039205 + sage: F.__hash__() == hash(F._gen_names) + True """ + return hash(self._gen_names) - return hash((self.__class__, self._names)) - - def __reduce__(self): + def __richcmp__(self, other, op): """ - Implement pickling. + Rich comparison of ``self`` and ``other``. - TESTS:: + EXAMPLES:: - sage: F. = FreeGroup() - sage: F.__reduce__()[1] - (, - (('a', 'b'),), {}) + sage: G1 = FreeGroup('a, b') + sage: gg = libgap.FreeGroup('x', 'y') + sage: G2 = FreeGroup('a, b', gap_group=gg) + sage: G1 == G2 + True + sage: G1 is G2 + False + sage: G3 = FreeGroup('x, y') + sage: G1 == G3 + False + sage: G2 == G3 + False """ - - from sage.structure.unique_representation import unreduce - return (unreduce, (self.__class__.__base__, (self._names, ), {})) + if not isinstance(other, self.__class__): + from sage.structure.richcmp import op_NE + return (op == op_NE) + return richcmp(self._gen_names, other._gen_names, op) def _repr_(self): """ @@ -770,7 +782,7 @@ def _repr_(self): sage: G._repr_() 'Free Group on generators {a, b}' """ - return 'Free Group on generators {' + ', '.join(self.variable_names()) + '}' + return 'Free Group on generators {' + ', '.join(self._gen_names) + '}' def rank(self): """ @@ -806,7 +818,7 @@ def _gap_init_(self): sage: G._gap_init_() 'FreeGroup(["x0", "x1", "x2"])' """ - gap_names = [ '"' + s + '"' for s in self.variable_names() ] + gap_names = ['"' + s + '"' for s in self._gen_names] gen_str = ', '.join(gap_names) return 'FreeGroup(['+gen_str+'])' @@ -862,9 +874,9 @@ def _element_constructor_(self, *args, **kwds): except AttributeError: return self.element_class(self, x, **kwds) if isinstance(P, FreeGroup_class): - names = {P._names[abs(i)-1] for i in x.Tietze()} - if names.issubset(self._names): - return self([i.sign()*(self._names.index(P._names[abs(i)-1])+1) + names = {P._gen_names[abs(i)-1] for i in x.Tietze()} + if names.issubset(self._gen_names): + return self([i.sign()*(self._gen_names.index(P._gen_names[abs(i)-1])+1) for i in x.Tietze()]) else: raise ValueError('generators of %s not in the group' % x) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index d1c6b6df832..1a190111476 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1349,7 +1349,7 @@ cdef class GapElement(RingElement): sage: F0 = libgap.FreeGroup(2) sage: F = F0.sage() - sage: F.gap() is F + sage: F.gap() is F0 True TESTS: @@ -1406,16 +1406,14 @@ cdef class GapElement(RingElement): elif self.IsFreeGroup(): from sage.groups.free_group import FreeGroup_class - self._set_compare_by_id() names = tuple(str(g) for g in self.GeneratorsOfGroup()) - return FreeGroup_class(names, libgap_free_group=self) + return FreeGroup_class(names, gap_group=self) elif self.IsFpGroup(): from sage.groups.free_group import FreeGroup from sage.groups.finitely_presented import FinitelyPresentedGroup - self._set_compare_by_id() - names = tuple(str(g) for g in self.FreeGroupOfFpGroup().GeneratorsOfGroup()) - F = FreeGroup(names) + # names = tuple(str(g).replace(".", "_") for g in self.FreeGroupOfFpGroup().GeneratorsOfGroup()) + F = self.FreeGroupOfFpGroup().sage() relations = tuple(F(rel.LetterRepAssocWord().sage()) for rel in self.RelatorsOfFpGroup()) return FinitelyPresentedGroup(F, relations, libgap_fpgroup=self) diff --git a/src/sage/structure/unique_representation.py b/src/sage/structure/unique_representation.py index ce0e84cd9be..0a52fbe1058 100644 --- a/src/sage/structure/unique_representation.py +++ b/src/sage/structure/unique_representation.py @@ -1164,7 +1164,7 @@ def unreduce(cls, args, keywords): return cls(*args, **keywords) -class UniqueRepresentation(CachedRepresentation, WithEqualityById): +class UniqueRepresentation(WithEqualityById, CachedRepresentation): r""" Classes derived from UniqueRepresentation inherit a unique representation behavior for their instances. From 65ffc52fb2ba48be1fc884a8ae58b3556cd86d59 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 11 Mar 2024 21:34:06 +0100 Subject: [PATCH 42/51] take out reduce and add UniqueRepresentation --- src/sage/groups/artin.py | 6 +++--- src/sage/groups/cubic_braid.py | 7 +++---- src/sage/groups/raag.py | 2 -- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index 49720518919..730bf84a628 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -21,11 +21,11 @@ # http://www.gnu.org/licenses/ # ***************************************************************************** -from sage.misc.cachefunc import cached_method -from sage.groups.free_group import FreeGroup -from sage.groups.finitely_presented import FinitelyPresentedGroup, FinitelyPresentedGroupElement from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix from sage.combinat.root_system.coxeter_group import CoxeterGroup +from sage.groups.free_group import FreeGroup +from sage.groups.finitely_presented import FinitelyPresentedGroup, FinitelyPresentedGroupElement +from sage.misc.cachefunc import cached_method from sage.rings.infinity import Infinity from sage.structure.richcmp import richcmp, rich_to_bool from sage.structure.unique_representation import UniqueRepresentation diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index 45f9845c702..5c5134cce4f 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -91,7 +91,8 @@ from sage.groups.braid import BraidGroup from sage.misc.cachefunc import cached_method from sage.rings.integer import Integer -from sage.structure.unique_representation import CachedRepresentation +from sage.structure.unique_representation import UniqueRepresentation + try: from sage.libs.gap.element import GapElement @@ -566,7 +567,7 @@ def conv2domain(laur_pol): # Class CubicBraidGroup # ############################################################################## -class CubicBraidGroup(FinitelyPresentedGroup): +class CubicBraidGroup(UniqueRepresentation, FinitelyPresentedGroup): r""" Factor groups of the Artin braid group mapping their generators to elements of order 3. @@ -804,8 +805,6 @@ def __init__(self, names, cbg_type=None): self._centralizing_element = None # image under nat. map of the former one in the proj. classical group return - __reduce__ = CachedRepresentation.__reduce__ - def _repr_(self): r""" Return a string representation. diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index 2222253fc48..810e89e859c 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -205,8 +205,6 @@ def __init__(self, G, names): FinitelyPresentedGroup.__init__(self, F, rels, category=Groups().Infinite()) - __reduce__ = CachedRepresentation.__reduce__ - def _repr_(self) -> str: """ Return a string representation of ``self``. From 65624882aa030c311e3b462a998d8b8b42783508 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 11 Mar 2024 21:58:55 +0100 Subject: [PATCH 43/51] lint --- src/sage/groups/finitely_presented.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index a9d2e0d3756..534edda4ba5 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -787,6 +787,11 @@ def __richcmp__(self, other, op): Rich comparison of ``self`` and ``other``. EXAMPLES:: + + sage: G1 = FreeGroup(2) / [(1, 2, 2, 1, 2, 1)] + sage: G2 = FreeGroup(2) / [(1, 2, 2, 1, 2, 1)] + sage: G1 == G2 + True """ if not isinstance(other, self.__class__): from sage.structure.richcmp import op_NE From 2dfc7e6ce1c2016abe81fcb6ff39e32d35563f0b Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 13 Mar 2024 13:45:45 +0100 Subject: [PATCH 44/51] more doctests --- src/sage/groups/finitely_presented.py | 20 ++++++++++++++++++++ src/sage/schemes/curves/zariski_vankampen.py | 2 -- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 534edda4ba5..68244679f80 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -753,6 +753,26 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): sage: J is H True + sage: A5 = libgap(AlternatingGroup(5)) + sage: A5gapfp = A5.IsomorphismFpGroup().Range() + sage: A5gapfp + + sage: A5sage = A5gapfp.sage(); A5sage; + Finitely presented group < A_5.1, A_5.2 | A_5.1^5*A_5.2^-5, A_5.1^5*(A_5.2^-1*A_5.1^-1)^2, (A_5.1^-2*A_5.2^2)^2 > + sage: A5sage.inject_variables() + Traceback (most recent call last): + ... + ValueError: variable names have not yet been set using self._assign_names(...) + + Check that pickling works + + sage: G = FreeGroup(2) / [2 * (1, 2, -1, -2)] + sage: loads(dumps(G)) + Finitely presented group < x0, x1 | (x0*x1*x0^-1*x1^-1)^2 > + sage: G.__reduce__()[1] + (, + (Free Group on generators {x0, x1}, ((x0*x1*x0^-1*x1^-1)^2,)),{}) + sage: TestSuite(H).run() sage: TestSuite(J).run() """ diff --git a/src/sage/schemes/curves/zariski_vankampen.py b/src/sage/schemes/curves/zariski_vankampen.py index 2a7e19ca553..6fe15b69f7c 100644 --- a/src/sage/schemes/curves/zariski_vankampen.py +++ b/src/sage/schemes/curves/zariski_vankampen.py @@ -50,7 +50,6 @@ from sage.geometry.voronoi_diagram import VoronoiDiagram from sage.graphs.graph import Graph from sage.groups.braid import BraidGroup -# from sage.groups.finitely_presented import wrap_FpGroup from sage.groups.free_group import FreeGroup from sage.groups.perm_gps.permgroup_named import SymmetricGroup from sage.libs.braiding import leftnormalform, rightnormalform @@ -67,7 +66,6 @@ from sage.rings.qqbar import QQbar from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RealField -# from sage.sets.set import Set roots_interval_cache = {} From c26f2d5d4e26f9cd7387a2cd1b2183294c5d585f Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 13 Mar 2024 15:21:45 +0100 Subject: [PATCH 45/51] lint --- src/sage/groups/finitely_presented.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 68244679f80..31a25387394 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -771,7 +771,7 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): Finitely presented group < x0, x1 | (x0*x1*x0^-1*x1^-1)^2 > sage: G.__reduce__()[1] (, - (Free Group on generators {x0, x1}, ((x0*x1*x0^-1*x1^-1)^2,)),{}) + (Free Group on generators {x0, x1}, ((x0*x1*x0^-1*x1^-1)^2,)),{}) sage: TestSuite(H).run() sage: TestSuite(J).run() From c5773fd7d3c0d8ea13b23c60e3735d3a8fa9345e Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 13 Mar 2024 16:33:01 +0100 Subject: [PATCH 46/51] minor change in one test --- src/sage/groups/finitely_presented.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 31a25387394..abf7ef8ff31 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -769,9 +769,8 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): sage: G = FreeGroup(2) / [2 * (1, 2, -1, -2)] sage: loads(dumps(G)) Finitely presented group < x0, x1 | (x0*x1*x0^-1*x1^-1)^2 > - sage: G.__reduce__()[1] - (, - (Free Group on generators {x0, x1}, ((x0*x1*x0^-1*x1^-1)^2,)),{}) + sage: G.__reduce__()[1][1] + (Free Group on generators {x0, x1}, ((x0*x1*x0^-1*x1^-1)^2,)) sage: TestSuite(H).run() sage: TestSuite(J).run() From 4724b6d082f6c93ebd7ea795d661aae173698c17 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 29 Apr 2024 15:44:22 +0200 Subject: [PATCH 47/51] deleting problematic backward tests --- src/sage/combinat/root_system/cartan_type.py | 36 -------------------- 1 file changed, 36 deletions(-) diff --git a/src/sage/combinat/root_system/cartan_type.py b/src/sage/combinat/root_system/cartan_type.py index e4526d5f079..8899b07bf21 100644 --- a/src/sage/combinat/root_system/cartan_type.py +++ b/src/sage/combinat/root_system/cartan_type.py @@ -3069,39 +3069,3 @@ def __getitem__(self, i): raise IndexError("index out of range") options = CartanType.options - -############################################################################## -# For backward compatibility - - -class CartanType_simple_finite: - def __setstate__(self, dict): - """ - Implements the unpickling of Cartan types pickled by Sage <= 4.0. - - EXAMPLES: - - This is the pickle for CartanType(["A", 4]):: - - sage: pg_CartanType_simple_finite = unpickle_global('sage.combinat.root_system.cartan_type', 'CartanType_simple_finite') - sage: si1 = unpickle_newobj(pg_CartanType_simple_finite, ()) - sage: from sage.misc.fpickle import unpickleModule - sage: pg_make_integer = unpickle_global('sage.rings.integer', 'make_integer') - sage: si2 = pg_make_integer('4') - sage: unpickle_build(si1, {'tools':unpickleModule('sage.combinat.root_system.type_A'), 't':['A', si2], 'letter':'A', 'n':si2}) - - sage: si1 - ['A', 4] - sage: si1.dynkin_diagram() # needs sage.graphs - O---O---O---O - 1 2 3 4 - A4 - - This is quite hacky; in particular unique representation is not preserved:: - - sage: si1 == CartanType(["A", 4]) # todo: not implemented - True - """ - T = CartanType([dict['letter'], dict['n']]) - self.__class__ = T.__class__ - self.__dict__ = T.__dict__ From 79b86c39bd71f26f4c7d6129821ba75acdae1106 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 10 Jun 2024 05:05:53 +0200 Subject: [PATCH 48/51] small changes --- src/sage/groups/finitely_presented.py | 4 +++- src/sage/groups/perm_gps/permgroup_named.py | 1 - src/sage/groups/raag.py | 1 - src/sage/schemes/curves/zariski_vankampen.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index bf8444d18f2..e9303ed71b3 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -756,7 +756,7 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): ... ValueError: variable names have not yet been set using self._assign_names(...) - Check that pickling works + Check that pickling works:: sage: G = FreeGroup(2) / [2 * (1, 2, -1, -2)] sage: loads(dumps(G)) @@ -803,6 +803,8 @@ def __richcmp__(self, other, op): sage: G2 = FreeGroup(2) / [(1, 2, 2, 1, 2, 1)] sage: G1 == G2 True + sage: G1 is G2 + True """ if not isinstance(other, self.__class__): from sage.structure.richcmp import op_NE diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 32ed58d5d53..c1f2683907b 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -716,7 +716,6 @@ def __init__(self, domain=None): sage: h = A.hom(codomain=A, im_gens=A.gens()) """ PermutationGroup_symalt.__init__(self, gap_group='AlternatingGroup(%s)' % len(domain), domain=domain) - self._gens = tuple(self(g) for g in self.gap().GeneratorsOfGroup()) def _repr_(self): """ diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index 810e89e859c..22c44cb2312 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -38,7 +38,6 @@ from sage.categories.groups import Groups from sage.categories.algebras_with_basis import AlgebrasWithBasis from sage.algebras.clifford_algebra_element import CohomologyRAAGElement -from sage.structure.unique_representation import CachedRepresentation from sage.typeset.ascii_art import ascii_art from sage.typeset.unicode_art import unicode_art diff --git a/src/sage/schemes/curves/zariski_vankampen.py b/src/sage/schemes/curves/zariski_vankampen.py index c222e56ea89..1dc90b51157 100644 --- a/src/sage/schemes/curves/zariski_vankampen.py +++ b/src/sage/schemes/curves/zariski_vankampen.py @@ -1543,7 +1543,7 @@ def braid2rels(L): P.TzGoGo() P.TzGoGo() gb = P.FpGroupPresentation().sage() - U = [_.Tietze() for _ in gb.relations()] + U = [rel.Tietze() for rel in gb.relations()] return U From b6d9f7f4d4f607572ddcf13c72d0b4c43da68fb3 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Mon, 10 Jun 2024 06:24:06 +0200 Subject: [PATCH 49/51] tests in richcmp --- src/sage/groups/finitely_presented.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index e9303ed71b3..df111866ec5 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -800,11 +800,11 @@ def __richcmp__(self, other, op): EXAMPLES:: sage: G1 = FreeGroup(2) / [(1, 2, 2, 1, 2, 1)] - sage: G2 = FreeGroup(2) / [(1, 2, 2, 1, 2, 1)] + sage: G2 = libgap(G1).sage() sage: G1 == G2 True sage: G1 is G2 - True + False """ if not isinstance(other, self.__class__): from sage.structure.richcmp import op_NE From 3636ffe2ede82ad58a8ce40320696929affa9e61 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Sat, 22 Jun 2024 23:47:50 +0200 Subject: [PATCH 50/51] typo --- src/sage/groups/finitely_presented.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 71b6321ef5f..c003f7ee7ac 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -781,7 +781,6 @@ def __init__(self, free_group, relations, category=None, libgap_fpgroup=None): ParentLibGAP.__init__(self, libgap_fpgroup) Group.__init__(self, category=category) -<<<<<<< HEAD def __hash__(self): """ Make hashable. From 7f74a17930058971d434ad50606fc10c4397b173 Mon Sep 17 00:00:00 2001 From: Enrique Artal Date: Wed, 10 Jul 2024 09:05:21 +0200 Subject: [PATCH 51/51] change tests due to unique representation change --- src/sage/categories/category_singleton.pyx | 2 +- src/sage/categories/sets_cat.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/category_singleton.pyx b/src/sage/categories/category_singleton.pyx index d907eedb9e8..6cd17581630 100644 --- a/src/sage/categories/category_singleton.pyx +++ b/src/sage/categories/category_singleton.pyx @@ -218,9 +218,9 @@ class Category_singleton(Category): , , , + , , , - , , , , diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 51eb9b14453..d8d278e80c5 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -136,9 +136,9 @@ class Sets(Category_singleton): + -