diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index c6628c587e2..2339394224e 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -21,13 +21,14 @@ # 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 class ArtinGroupElement(FinitelyPresentedGroupElement): @@ -329,7 +330,7 @@ def _left_normal_form_coxeter(self): return tuple([-delta] + form) -class ArtinGroup(FinitelyPresentedGroup): +class ArtinGroup(UniqueRepresentation, FinitelyPresentedGroup): r""" An Artin group. diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index 93b52433c35..d607435337a 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -91,6 +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 UniqueRepresentation + try: from sage.libs.gap.element import GapElement @@ -565,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. @@ -750,7 +752,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") - if cbg_type is None: cbg_type = CubicBraidGroup.type.Coxeter if not isinstance(cbg_type, CubicBraidGroup.type): diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 4ac2faa783b..8eb469118d3 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 @@ -143,7 +142,8 @@ 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.richcmp import richcmp, richcmp_method +from sage.structure.unique_representation import CachedRepresentation class GroupMorphismWithGensImages(SetMorphism): @@ -362,51 +362,7 @@ 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()) - relations = tuple(free_group(rel.UnderlyingElement()) - for rel in libgap_fpgroup.RelatorsOfFpGroup()) - return FinitelyPresentedGroup(free_group, relations) - - -class RewritingSystem: +class RewritingSystem(): """ A class that wraps GAP's rewriting systems. @@ -730,7 +686,8 @@ def make_confluent(self): raise ValueError('could not make the system confluent') -class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, Group, ParentLibGAP): +@richcmp_method +class FinitelyPresentedGroup(GroupMixinLibGAP, CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's Finitely Presented Groups. @@ -739,7 +696,9 @@ class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, 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:: @@ -770,7 +729,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. @@ -786,6 +745,25 @@ def __init__(self, free_group, relations, category=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][1] + (Free Group on generators {x0, x1}, ((x0*x1*x0^-1*x1^-1)^2,)) + sage: TestSuite(H).run() sage: TestSuite(J).run() """ @@ -794,11 +772,47 @@ def __init__(self, free_group, relations, category=None): assert isinstance(relations, tuple) 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) + 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 __hash__(self): + """ + Make hashable. + + EXAMPLES:: + + sage: G = FreeGroup(2) / [(1, 2, 2, 1)] + sage: G.__hash__() == hash((G.free_group(), G.relations())) + True + """ + return hash((self._free_group, self._relations)) + + def __richcmp__(self, other, op): + """ + Rich comparison of ``self`` and ``other``. + + EXAMPLES:: + + sage: G1 = FreeGroup(2) / [(1, 2, 2, 1, 2, 1)] + sage: G2 = libgap(G1).sage() + sage: G1 == G2 + True + sage: G1 is G2 + False + """ + 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) -> str: """ Return a string representation. @@ -812,7 +826,7 @@ def _repr_(self) -> str: 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 + ' >' @@ -1357,7 +1371,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) @@ -1365,7 +1379,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): @@ -1438,12 +1452,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) @@ -1455,21 +1466,26 @@ 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: Uses GAP. """ II = self.gap().IsomorphismSimplifiedFpGroup() - codomain = wrap_FpGroup(II.Range()) - 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()] + 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): """ @@ -1543,48 +1559,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/free_group.py b/src/sage/groups/free_group.py index 207c80ea348..bc952896e67 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 @@ -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, richcmp_method def is_FreeGroup(x): @@ -672,56 +673,11 @@ 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) -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 :issue:`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, libgap_free_group) - - -class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): +@richcmp_method +class FreeGroup_class(CachedRepresentation, Group, ParentLibGAP): """ A class that wraps GAP's FreeGroup. @@ -736,7 +692,7 @@ class FreeGroup_class(UniqueRepresentation, Group, ParentLibGAP): """ Element = FreeGroupElement - def __init__(self, generator_names, libgap_free_group=None): + def __init__(self, generator_names, gap_group=None): """ Python constructor. @@ -757,15 +713,55 @@ 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): + """ + Make hashable. + + EXAMPLES:: + + sage: F = FreeGroup(3) + sage: F.__hash__() == hash(F._gen_names) + True + """ + return hash(self._gen_names) + + def __richcmp__(self, other, op): + """ + Rich comparison of ``self`` and ``other``. + + EXAMPLES:: + + 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 + """ + 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): """ @@ -777,7 +773,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): """ @@ -811,7 +807,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+'])' @@ -867,9 +863,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 d70e8bad6b2..24db9c07cb8 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1320,12 +1320,22 @@ cdef class GapElement(RingElement): sage: p.sage().parent() Fraction Field of Univariate Polynomial Ring in x over Integer Ring + sage: G0 = libgap.SymplecticGroup(4,2) + sage: P = G0.IsomorphismFpGroup().Range() + sage: G = P.sage() + sage: G.gap() == P + True + + sage: F0 = libgap.FreeGroup(2) + sage: F = F0.sage() + sage: F.gap() is F0 + True + TESTS: Check :issue:`30496`:: sage: x = libgap.Integers.Indeterminate("x") - sage: p = x^2 - 2*x sage: p.sage() x^2 - 2*x @@ -1373,6 +1383,20 @@ 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 + names = tuple(str(g) for g in self.GeneratorsOfGroup()) + return FreeGroup_class(names, gap_group=self) + + elif self.IsFpGroup(): + from sage.groups.free_group import FreeGroup + from sage.groups.finitely_presented import FinitelyPresentedGroup + # 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) + raise NotImplementedError('cannot construct equivalent Sage object') diff --git a/src/sage/schemes/curves/zariski_vankampen.py b/src/sage/schemes/curves/zariski_vankampen.py index b2b9d7cd5b2..925d1d9f2d0 100755 --- a/src/sage/schemes/curves/zariski_vankampen.py +++ b/src/sage/schemes/curves/zariski_vankampen.py @@ -51,7 +51,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.matrix.constructor import matrix @@ -1537,7 +1536,7 @@ def braid2rels(L): P.SetTzOptions(dic) P.TzGoGo() P.TzGoGo() - gb = wrap_FpGroup(P.FpGroupPresentation()) + gb = P.FpGroupPresentation().sage() U = [rel.Tietze() for rel in gb.relations()] return U