Skip to content

Commit d593324

Browse files
author
Release Manager
committed
gh-37128: Save and load finitely presented groups coming from libgap groups <!-- Describe your changes here in detail --> At this point it is not possible to load a saved finitely presented group that comes from a libgap group, see an example in #37061 One possible cause is the use of a general `__reduce__` method for free groups. At least, adding such a method allows to load free groups or finitely presented groups obtained from a libgap group using `wrapFreeGroup` or `wrapFpGroup`. It fixes #37061 and it would simplify some code in #36768 With these changes, free and finitely presented groups, included libgap groups, can be pickled. ### 📝 Checklist - [X] The title is concise, informative, and self-explanatory. - [X] The description explains in detail what this PR is about. - [X] I have linked a relevant issue or discussion. - [X] I have created tests covering the changes. - [X] I have updated the documentation accordingly. URL: #37128 Reported by: Enrique Manuel Artal Bartolo Reviewer(s): Enrique Manuel Artal Bartolo, Travis Scrimshaw
2 parents d1f99d1 + e7d12f6 commit d593324

File tree

6 files changed

+198
-165
lines changed

6 files changed

+198
-165
lines changed

src/sage/groups/artin.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
# http://www.gnu.org/licenses/
2222
# *****************************************************************************
2323

24-
from sage.misc.cachefunc import cached_method
25-
from sage.groups.free_group import FreeGroup
26-
from sage.groups.finitely_presented import FinitelyPresentedGroup, FinitelyPresentedGroupElement
2724
from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix
2825
from sage.combinat.root_system.coxeter_group import CoxeterGroup
26+
from sage.groups.free_group import FreeGroup
27+
from sage.groups.finitely_presented import FinitelyPresentedGroup, FinitelyPresentedGroupElement
28+
from sage.misc.cachefunc import cached_method
2929
from sage.rings.infinity import Infinity
3030
from sage.structure.richcmp import richcmp, rich_to_bool
31+
from sage.structure.unique_representation import UniqueRepresentation
3132

3233

3334
class ArtinGroupElement(FinitelyPresentedGroupElement):
@@ -329,7 +330,7 @@ def _left_normal_form_coxeter(self):
329330
return tuple([-delta] + form)
330331

331332

332-
class ArtinGroup(FinitelyPresentedGroup):
333+
class ArtinGroup(UniqueRepresentation, FinitelyPresentedGroup):
333334
r"""
334335
An Artin group.
335336

src/sage/groups/cubic_braid.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@
9191
from sage.groups.braid import BraidGroup
9292
from sage.misc.cachefunc import cached_method
9393
from sage.rings.integer import Integer
94+
from sage.structure.unique_representation import UniqueRepresentation
95+
9496

9597
try:
9698
from sage.libs.gap.element import GapElement
@@ -565,7 +567,7 @@ def conv2domain(laur_pol):
565567
# Class CubicBraidGroup
566568
#
567569
##############################################################################
568-
class CubicBraidGroup(FinitelyPresentedGroup):
570+
class CubicBraidGroup(UniqueRepresentation, FinitelyPresentedGroup):
569571
r"""
570572
Factor groups of the Artin braid group mapping their generators to elements
571573
of order 3.
@@ -750,7 +752,6 @@ def __init__(self, names, cbg_type=None):
750752
n = Integer(len(names))
751753
if n < 1:
752754
raise ValueError("the number of strands must be an integer larger than one")
753-
754755
if cbg_type is None:
755756
cbg_type = CubicBraidGroup.type.Coxeter
756757
if not isinstance(cbg_type, CubicBraidGroup.type):

src/sage/groups/finitely_presented.py

Lines changed: 109 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@
130130

131131
from sage.arith.misc import GCD as gcd
132132
from sage.categories.morphism import SetMorphism
133-
from sage.functions.generalized import sign
134133
from sage.groups.free_group import FreeGroup
135134
from sage.groups.free_group import FreeGroupElement
136135
from sage.groups.group import Group
@@ -143,7 +142,8 @@
143142
from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
144143
from sage.rings.rational_field import QQ
145144
from sage.sets.set import Set
146-
from sage.structure.unique_representation import UniqueRepresentation
145+
from sage.structure.richcmp import richcmp, richcmp_method
146+
from sage.structure.unique_representation import CachedRepresentation
147147

148148

149149
class GroupMorphismWithGensImages(SetMorphism):
@@ -362,51 +362,7 @@ def __call__(self, *values, **kwds):
362362
return super().__call__(values)
363363

364364

365-
def wrap_FpGroup(libgap_fpgroup):
366-
"""
367-
Wrap a GAP finitely presented group.
368-
369-
This function changes the comparison method of
370-
``libgap_free_group`` to comparison by Python ``id``. If you want
371-
to put the LibGAP free group into a container ``(set, dict)`` then you
372-
should understand the implications of
373-
:meth:`~sage.libs.gap.element.GapElement._set_compare_by_id`. To
374-
be safe, it is recommended that you just work with the resulting
375-
Sage :class:`FinitelyPresentedGroup`.
376-
377-
INPUT:
378-
379-
- ``libgap_fpgroup`` -- a LibGAP finitely presented group
380-
381-
OUTPUT: a Sage :class:`FinitelyPresentedGroup`
382-
383-
EXAMPLES:
384-
385-
First construct a LibGAP finitely presented group::
386-
387-
sage: F = libgap.FreeGroup(['a', 'b'])
388-
sage: a_cubed = F.GeneratorsOfGroup()[0] ^ 3
389-
sage: P = F / libgap([ a_cubed ]); P
390-
<fp group of size infinity on the generators [ a, b ]>
391-
sage: type(P)
392-
<class 'sage.libs.gap.element.GapElement'>
393-
394-
Now wrap it::
395-
396-
sage: from sage.groups.finitely_presented import wrap_FpGroup
397-
sage: wrap_FpGroup(P)
398-
Finitely presented group < a, b | a^3 >
399-
"""
400-
assert libgap_fpgroup.IsFpGroup()
401-
libgap_fpgroup._set_compare_by_id()
402-
from sage.groups.free_group import wrap_FreeGroup
403-
free_group = wrap_FreeGroup(libgap_fpgroup.FreeGroupOfFpGroup())
404-
relations = tuple(free_group(rel.UnderlyingElement())
405-
for rel in libgap_fpgroup.RelatorsOfFpGroup())
406-
return FinitelyPresentedGroup(free_group, relations)
407-
408-
409-
class RewritingSystem:
365+
class RewritingSystem():
410366
"""
411367
A class that wraps GAP's rewriting systems.
412368
@@ -730,7 +686,8 @@ def make_confluent(self):
730686
raise ValueError('could not make the system confluent')
731687

732688

733-
class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, Group, ParentLibGAP):
689+
@richcmp_method
690+
class FinitelyPresentedGroup(GroupMixinLibGAP, CachedRepresentation, Group, ParentLibGAP):
734691
"""
735692
A class that wraps GAP's Finitely Presented Groups.
736693
@@ -739,7 +696,9 @@ class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, Group, Pare
739696
You should use
740697
:meth:`~sage.groups.free_group.FreeGroup_class.quotient` to
741698
construct finitely presented groups as quotients of free
742-
groups.
699+
groups. Any class inheriting this one should define
700+
``__reduce__ = CachedRepresentation.__reduce__``
701+
after importing ``CachedRepresentation``.
743702
744703
EXAMPLES::
745704
@@ -770,7 +729,7 @@ class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, Group, Pare
770729
"""
771730
Element = FinitelyPresentedGroupElement
772731

773-
def __init__(self, free_group, relations, category=None):
732+
def __init__(self, free_group, relations, category=None, libgap_fpgroup=None):
774733
"""
775734
The Python constructor.
776735
@@ -786,6 +745,25 @@ def __init__(self, free_group, relations, category=None):
786745
sage: J is H
787746
True
788747
748+
sage: A5 = libgap(AlternatingGroup(5))
749+
sage: A5gapfp = A5.IsomorphismFpGroup().Range()
750+
sage: A5gapfp
751+
<fp group of size 60 on the generators [ A_5.1, A_5.2 ]>
752+
sage: A5sage = A5gapfp.sage(); A5sage;
753+
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 >
754+
sage: A5sage.inject_variables()
755+
Traceback (most recent call last):
756+
...
757+
ValueError: variable names have not yet been set using self._assign_names(...)
758+
759+
Check that pickling works::
760+
761+
sage: G = FreeGroup(2) / [2 * (1, 2, -1, -2)]
762+
sage: loads(dumps(G))
763+
Finitely presented group < x0, x1 | (x0*x1*x0^-1*x1^-1)^2 >
764+
sage: G.__reduce__()[1][1]
765+
(Free Group on generators {x0, x1}, ((x0*x1*x0^-1*x1^-1)^2,))
766+
789767
sage: TestSuite(H).run()
790768
sage: TestSuite(J).run()
791769
"""
@@ -794,11 +772,47 @@ def __init__(self, free_group, relations, category=None):
794772
assert isinstance(relations, tuple)
795773
self._free_group = free_group
796774
self._relations = relations
797-
self._assign_names(free_group.variable_names())
798-
parent_gap = free_group.gap() / libgap([rel.gap() for rel in relations])
799-
ParentLibGAP.__init__(self, parent_gap)
775+
try:
776+
self._assign_names(free_group.variable_names())
777+
except ValueError:
778+
pass
779+
if libgap_fpgroup is None:
780+
libgap_fpgroup = free_group.gap() / libgap([rel.gap() for rel in relations])
781+
ParentLibGAP.__init__(self, libgap_fpgroup)
800782
Group.__init__(self, category=category)
801783

784+
def __hash__(self):
785+
"""
786+
Make hashable.
787+
788+
EXAMPLES::
789+
790+
sage: G = FreeGroup(2) / [(1, 2, 2, 1)]
791+
sage: G.__hash__() == hash((G.free_group(), G.relations()))
792+
True
793+
"""
794+
return hash((self._free_group, self._relations))
795+
796+
def __richcmp__(self, other, op):
797+
"""
798+
Rich comparison of ``self`` and ``other``.
799+
800+
EXAMPLES::
801+
802+
sage: G1 = FreeGroup(2) / [(1, 2, 2, 1, 2, 1)]
803+
sage: G2 = libgap(G1).sage()
804+
sage: G1 == G2
805+
True
806+
sage: G1 is G2
807+
False
808+
"""
809+
if not isinstance(other, self.__class__):
810+
from sage.structure.richcmp import op_NE
811+
return (op == op_NE)
812+
self_data = (self._free_group, self._relations)
813+
other_data = (other._free_group, other._relations)
814+
return richcmp(self_data, other_data, op)
815+
802816
def _repr_(self) -> str:
803817
"""
804818
Return a string representation.
@@ -812,7 +826,7 @@ def _repr_(self) -> str:
812826
sage: H._repr_()
813827
'Finitely presented group < a, b | a, b^3 >'
814828
"""
815-
gens = ', '.join(self.variable_names())
829+
gens = ', '.join(self._free_group._gen_names)
816830
rels = ', '.join(str(r) for r in self.relations())
817831
return 'Finitely presented group ' + '< ' + gens + ' | ' + rels + ' >'
818832

@@ -1357,15 +1371,15 @@ def abelianization_map(self):
13571371
hom_ab_fp = ab_libgap.IsomorphismFpGroup()
13581372
ab_libgap_fp = hom_ab_fp.Range()
13591373
hom_simply = ab_libgap_fp.IsomorphismSimplifiedFpGroup()
1360-
ab = wrap_FpGroup(hom_simply.Range())
1374+
ab = hom_simply.Range().sage()
13611375
images = []
13621376
for f in self.gens():
13631377
f0 = hom_ab_libgap.Image(f)
13641378
f1 = hom_ab_fp.Image(f0)
13651379
f2 = hom_simply.Image(f1)
13661380
L = f2.UnderlyingElement().LetterRepAssocWord()
13671381
images.append(ab([int(j) for j in L]))
1368-
return self.hom(codomain=ab, im_gens=images)
1382+
return self.hom(codomain=ab, im_gens=images, check=False)
13691383

13701384
@cached_method
13711385
def abelianization_to_algebra(self, ring=QQ):
@@ -1438,12 +1452,9 @@ def simplification_isomorphism(self):
14381452
sage: H = G / [a*b*c, a*b^2, c*b/c^2]
14391453
sage: I = H.simplification_isomorphism()
14401454
sage: I
1441-
Generic morphism:
1455+
Group morphism:
14421456
From: Finitely presented group < a, b, c | a*b*c, a*b^2, c*b*c^-2 >
14431457
To: Finitely presented group < b | >
1444-
Defn: a |--> b^-2
1445-
b |--> b
1446-
c |--> b
14471458
sage: I(a)
14481459
b^-2
14491460
sage: I(b)
@@ -1455,21 +1466,26 @@ def simplification_isomorphism(self):
14551466
14561467
sage: F = FreeGroup(1)
14571468
sage: G = F.quotient([F.0])
1458-
sage: G.simplification_isomorphism()
1459-
Generic morphism:
1469+
sage: h = G.simplification_isomorphism(); h
1470+
Group morphism:
14601471
From: Finitely presented group < x | x >
14611472
To: Finitely presented group < | >
1462-
Defn: x |--> 1
1473+
sage: h(G.gen(0))
1474+
1
14631475
14641476
ALGORITHM:
14651477
14661478
Uses GAP.
14671479
"""
14681480
II = self.gap().IsomorphismSimplifiedFpGroup()
1469-
codomain = wrap_FpGroup(II.Range())
1470-
phi = lambda x: codomain(II.ImageElm(x.gap()))
1471-
HS = self.Hom(codomain)
1472-
return GroupMorphismWithGensImages(HS, phi)
1481+
cod = II.Range().sage()
1482+
phi = [cod(II.ImageElm(x)) for x in self.gap().GeneratorsOfGroup()]
1483+
return self.hom(codomain=cod, im_gens=phi, check=False)
1484+
# II = self.gap().IsomorphismSimplifiedFpGroup()
1485+
# codomain = II.Range().sage()
1486+
# phi = lambda x: codomain(II.ImageElm(x.gap()))
1487+
# HS = self.Hom(codomain)
1488+
# return GroupMorphismWithGensImages(HS, phi)
14731489

14741490
def simplified(self):
14751491
"""
@@ -1543,48 +1559,44 @@ def epimorphisms(self, H):
15431559
sage: F = FreeGroup(3)
15441560
sage: G = F / [F([1, 2, 3, 1, 2, 3]), F([1, 1, 1])]
15451561
sage: H = AlternatingGroup(3)
1546-
sage: G.epimorphisms(H)
1547-
[Generic morphism:
1548-
From: Finitely presented group < x0, x1, x2 | x0*x1*x2*x0*x1*x2, x0^3 >
1549-
To: Alternating group of order 3!/2 as a permutation group
1550-
Defn: x0 |--> ()
1551-
x1 |--> (1,3,2)
1552-
x2 |--> (1,2,3),
1553-
Generic morphism:
1554-
From: Finitely presented group < x0, x1, x2 | x0*x1*x2*x0*x1*x2, x0^3 >
1555-
To: Alternating group of order 3!/2 as a permutation group
1556-
Defn: x0 |--> (1,3,2)
1557-
x1 |--> ()
1558-
x2 |--> (1,2,3),
1559-
Generic morphism:
1560-
From: Finitely presented group < x0, x1, x2 | x0*x1*x2*x0*x1*x2, x0^3 >
1561-
To: Alternating group of order 3!/2 as a permutation group
1562-
Defn: x0 |--> (1,3,2)
1563-
x1 |--> (1,2,3)
1564-
x2 |--> (),
1565-
Generic morphism:
1566-
From: Finitely presented group < x0, x1, x2 | x0*x1*x2*x0*x1*x2, x0^3 >
1567-
To: Alternating group of order 3!/2 as a permutation group
1568-
Defn: x0 |--> (1,2,3)
1569-
x1 |--> (1,2,3)
1570-
x2 |--> (1,2,3)]
1562+
sage: for quo in G.epimorphisms(H):
1563+
....: for a in G.gens():
1564+
....: print(a, "|-->", quo(a))
1565+
....: print("-----")
1566+
x0 |--> ()
1567+
x1 |--> (1,3,2)
1568+
x2 |--> (1,2,3)
1569+
-----
1570+
x0 |--> (1,3,2)
1571+
x1 |--> ()
1572+
x2 |--> (1,2,3)
1573+
-----
1574+
x0 |--> (1,3,2)
1575+
x1 |--> (1,2,3)
1576+
x2 |--> ()
1577+
-----
1578+
x0 |--> (1,2,3)
1579+
x1 |--> (1,2,3)
1580+
x2 |--> (1,2,3)
1581+
-----
15711582
15721583
ALGORITHM:
15731584
15741585
Uses libgap's GQuotients function.
15751586
"""
1576-
from sage.misc.misc_c import prod
1577-
HomSpace = self.Hom(H)
1587+
# from sage.misc.misc_c import prod
1588+
# HomSpace = self.Hom(H)
15781589
Gg = libgap(self)
15791590
Hg = libgap(H)
15801591
gquotients = Gg.GQuotients(Hg)
15811592
res = []
15821593
# the following closure is needed to attach a specific value of quo to
15831594
# each function in the different morphisms
1584-
fmap = lambda tup: (lambda a: H(prod(tup[abs(i)-1]**sign(i) for i in a.Tietze())))
1595+
# fmap = lambda tup: (lambda a: H(prod(tup[abs(i)-1]**sign(i) for i in a.Tietze())))
15851596
for quo in gquotients:
1586-
tup = tuple(H(quo.ImageElm(i.gap()).sage()) for i in self.gens())
1587-
fhom = GroupMorphismWithGensImages(HomSpace, fmap(tup))
1597+
# tup = tuple(H(quo.ImageElm(i.gap()).sage()) for i in self.gens())
1598+
# fhom = GroupMorphismWithGensImages(HomSpace, fmap(tup))
1599+
fhom = self.hom(codomain=H, im_gens=[H(quo.ImageElm(a.gap())) for a in self.gens()])
15881600
res.append(fhom)
15891601
return res
15901602

0 commit comments

Comments
 (0)