diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index 90cdcaa42fd..ea8f9edc51b 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -1622,78 +1622,95 @@ class AbelianGroup_subgroup(AbelianGroup_class): There should be a way to coerce an element of a subgroup into the ambient group. + + EXAMPLES:: + + sage: # needs sage.libs.gap # optional - gap_package_polycyclic + sage: F = AbelianGroup(5, [30,64,729], names=list("abcde")) + sage: a,b,c,d,e = F.gens() + sage: F.subgroup([a^3,b]) + Multiplicative Abelian subgroup isomorphic to Z x Z generated by {a^3, b} + sage: F.subgroup([c]) + Multiplicative Abelian subgroup isomorphic to C2 x C3 x C5 generated by {c} + sage: F.subgroup([a, c]) + Multiplicative Abelian subgroup isomorphic to C2 x C3 x C5 x Z generated by {a, c} + sage: F.subgroup([a, b*c]) + Multiplicative Abelian subgroup isomorphic to Z x Z generated by {a, b*c} + sage: F.subgroup([b*c, d]) + Multiplicative Abelian subgroup isomorphic to C64 x Z generated by {b*c, d} + sage: F.subgroup([a*b, c^6, d], names=list("xyz")) + Multiplicative Abelian subgroup isomorphic to C5 x C64 x Z generated by {a*b, c^6, d} + sage: H. = F.subgroup([a*b, c^6, d]); H + Multiplicative Abelian subgroup isomorphic to C5 x C64 x Z generated by {a*b, c^6, d} + sage: G = F.subgroup([a*b, c^6, d], names=list("xyz")); G + Multiplicative Abelian subgroup isomorphic to C5 x C64 x Z generated by {a*b, c^6, d} + sage: x,y,z = G.gens() + sage: x.order() + +Infinity + sage: y.order() + 5 + sage: z.order() + 64 + sage: A = AbelianGroup(5, [3, 5, 5, 7, 8], names="abcde") + sage: a,b,c,d,e = A.gens() + sage: A.subgroup([a,b]) + Multiplicative Abelian subgroup isomorphic to C3 x C5 generated by {a, b} + sage: A.subgroup([a,b,c,d^2,e]) + Multiplicative Abelian subgroup isomorphic to C3 x C5 x C5 x C7 x C8 generated by {a, b, c, d^2, e} + sage: A.subgroup([a, b, c, d^2, e^2]) + Multiplicative Abelian subgroup isomorphic to C3 x C4 x C5 x C5 x C7 generated by {a, b, c, d^2, e^2} + sage: B = A.subgroup([a^3, b, c, d, e^2]); B + Multiplicative Abelian subgroup isomorphic to C4 x C5 x C5 x C7 generated by {b, c, d, e^2} + sage: B.gens_orders() + (5, 5, 7, 4) + sage: A = AbelianGroup(4,[1009, 2003, 3001, 4001], names="abcd") + sage: a,b,c,d = A.gens() + + sage: # long time + sage: B = A.subgroup([a^3,b,c,d]) + sage: B.gens_orders() + (1009, 2003, 3001, 4001) + sage: A.order() + 24266473210027 + sage: B.order() + 24266473210027 + sage: A = AbelianGroup(4, [1008, 2003, 3001, 4001], names="abcd") + sage: a,b,c,d = A.gens() + sage: B = A.subgroup([a^3,b,c,d]); B + Multiplicative Abelian subgroup isomorphic + to C3 x C7 x C16 x C2003 x C3001 x C4001 generated by {a^3, b, c, d} + + Infinite groups can also be handled:: + + sage: # needs sage.libs.gap # optional - gap_package_polycyclic + sage: G = AbelianGroup([3,4,0], names="abc") + sage: a,b,c = G.gens() + sage: F = G.subgroup([a, b^2, c]); F + Multiplicative Abelian subgroup isomorphic to C2 x C3 x Z + generated by {a, b^2, c} + sage: F.gens_orders() + (3, 2, 0) + sage: F.gens() + (a, b^2, c) + sage: F.order() + +Infinity """ def __init__(self, ambient, gens, names="f", category=None): """ - EXAMPLES:: + Initialize ``self``. - sage: # needs sage.libs.gap # optional - gap_package_polycyclic - sage: F = AbelianGroup(5, [30,64,729], names=list("abcde")) - sage: a,b,c,d,e = F.gens() - sage: F.subgroup([a^3,b]) - Multiplicative Abelian subgroup isomorphic to Z x Z generated by {a^3, b} - sage: F.subgroup([c]) - Multiplicative Abelian subgroup isomorphic to C2 x C3 x C5 generated by {c} - sage: F.subgroup([a, c]) - Multiplicative Abelian subgroup isomorphic to C2 x C3 x C5 x Z generated by {a, c} - sage: F.subgroup([a, b*c]) - Multiplicative Abelian subgroup isomorphic to Z x Z generated by {a, b*c} - sage: F.subgroup([b*c, d]) - Multiplicative Abelian subgroup isomorphic to C64 x Z generated by {b*c, d} - sage: F.subgroup([a*b, c^6, d], names=list("xyz")) - Multiplicative Abelian subgroup isomorphic to C5 x C64 x Z generated by {a*b, c^6, d} - sage: H. = F.subgroup([a*b, c^6, d]); H - Multiplicative Abelian subgroup isomorphic to C5 x C64 x Z generated by {a*b, c^6, d} - sage: G = F.subgroup([a*b, c^6, d], names=list("xyz")); G - Multiplicative Abelian subgroup isomorphic to C5 x C64 x Z generated by {a*b, c^6, d} - sage: x,y,z = G.gens() - sage: x.order() - +Infinity - sage: y.order() - 5 - sage: z.order() - 64 - sage: A = AbelianGroup(5, [3, 5, 5, 7, 8], names="abcde") - sage: a,b,c,d,e = A.gens() - sage: A.subgroup([a,b]) - Multiplicative Abelian subgroup isomorphic to C3 x C5 generated by {a, b} - sage: A.subgroup([a,b,c,d^2,e]) - Multiplicative Abelian subgroup isomorphic to C3 x C5 x C5 x C7 x C8 generated by {a, b, c, d^2, e} - sage: A.subgroup([a, b, c, d^2, e^2]) - Multiplicative Abelian subgroup isomorphic to C3 x C4 x C5 x C5 x C7 generated by {a, b, c, d^2, e^2} - sage: B = A.subgroup([a^3, b, c, d, e^2]); B - Multiplicative Abelian subgroup isomorphic to C4 x C5 x C5 x C7 generated by {b, c, d, e^2} - sage: B.gens_orders() - (5, 5, 7, 4) - sage: A = AbelianGroup(4,[1009, 2003, 3001, 4001], names="abcd") - sage: a,b,c,d = A.gens() - sage: B = A.subgroup([a^3,b,c,d]) - sage: B.gens_orders() - (1009, 2003, 3001, 4001) - sage: A.order() - 24266473210027 - sage: B.order() - 24266473210027 - sage: A = AbelianGroup(4, [1008, 2003, 3001, 4001], names="abcd") - sage: a,b,c,d = A.gens() - sage: B = A.subgroup([a^3,b,c,d]); B - Multiplicative Abelian subgroup isomorphic - to C3 x C7 x C16 x C2003 x C3001 x C4001 generated by {a^3, b, c, d} - - Infinite groups can also be handled:: + TESTS:: sage: # needs sage.libs.gap # optional - gap_package_polycyclic - sage: G = AbelianGroup([3,4,0], names="abc") - sage: a,b,c = G.gens() - sage: F = G.subgroup([a, b^2, c]); F - Multiplicative Abelian subgroup isomorphic to C2 x C3 x Z - generated by {a, b^2, c} - sage: F.gens_orders() - (3, 2, 0) - sage: F.gens() - (a, b^2, c) - sage: F.order() - +Infinity + sage: S. = AbelianGroup(5, [30,64,729]) + sage: H = G.subgroup([a^3,b]) + sage: TestSuite(H).run() # long time + sage: G. = AbelianGroup([3,4,0]) + sage: H = G.subgroup([a, b^2, c^3]) + sage: TestSuite(H).run() # long time + sage: G. = AbelianGroup([0,0,6,3]) + sage: H = G.subgroup([a*b, a/b, (a*b)^5, (a*b)^3, c^2, c*d]) + sage: TestSuite(H).run() # long time Testing issue :issue:`18863`:: @@ -1709,26 +1726,33 @@ def __init__(self, ambient, gens, names="f", category=None): raise TypeError("gens (=%s) must be a tuple" % gens) self._ambient_group = ambient - H_gens = tuple(x for x in gens if x != ambient.one()) # clean entry - self._gens = H_gens - - H = libgap(ambient).Subgroup(H_gens) - - invs = H.TorsionSubgroup().AbelianInvariants().sage() - gens_orders = tuple([ZZ(order_sage) for g in H.GeneratorsOfGroup() - if (order_sage := g.Order().sage()) is not infinity]) - - rank = len([1 for g in H.GeneratorsOfGroup() - if g.Order().sage() is infinity]) - invs += [0] * rank + # get rid of the trivial identities + gens = [x for x in gens if x != ambient.one()] + + G = libgap(ambient) + H = G.Subgroup(gens) + indep_gens = H.IndependentGeneratorsOfAbelianGroup() + # The internal structure in GAP for finite and infinite Abelian groups + # are different, so our reconstruction of the elements depends on this. + if G.IsFinite(): + lifted_gens = [] + amb_gens = ambient.gens() + for h in indep_gens: + x = libgap.Factorization(G, h) + data = libgap.ExtRepOfObj(x).sage() # (indice, power, indice, power, etc) + indices = data[0::2] + powers = data[1::2] + elt = ambient.prod(amb_gens[i-1] ** e for i, e in zip(indices, powers)) + lifted_gens.append(elt) + self._lifted_gens = tuple(lifted_gens) + else: + self._lifted_gens = tuple([ambient(h.Exponents().sage()) + for h in indep_gens]) + invs = [ZZ(ord) if (ord := h.Order().sage()) is not infinity else ZZ.zero() + for h in indep_gens] - gens_orders += (ZZ.zero(),) * rank - self._abinvs = invs - invs = tuple(ZZ(i) for i in invs) - - if category is None: - category = Groups().Commutative().Subobjects() - AbelianGroup_class.__init__(self, gens_orders, names, category=category) + category = Groups().Commutative().Subobjects().or_subcategory(category) + AbelianGroup_class.__init__(self, tuple(invs), names, category=category) def __contains__(self, x): """ @@ -1778,8 +1802,8 @@ def __contains__(self, x): amb_inv = self.ambient_group().gens_orders() inv_basis = diagonal_matrix(ZZ, amb_inv) gens_basis = matrix( - ZZ, len(self._gens), len(amb_inv), - [g.list() for g in self._gens] + ZZ, len(self._lifted_gens), len(amb_inv), + [g.list() for g in self._lifted_gens] ) return (vector(ZZ, x.list()) in inv_basis.stack(gens_basis).row_module()) @@ -1787,7 +1811,7 @@ def __contains__(self, x): def ambient_group(self): """ - Return the ambient group related to self. + Return the ambient group related to ``self``. OUTPUT: @@ -1825,8 +1849,8 @@ def equals(left, right): Multiplicative Abelian group isomorphic to C2 x C3 x C4 sage: a,b,c = G.gens() sage: F = G.subgroup([a,b^2]); F - Multiplicative Abelian subgroup isomorphic to C2 x C3 generated by {a, b^2} - sage: F = AbelianGroup([4, 4]) + sage: H = G.subgroup(list(G)) + sage: hash(H) == hash(G.subgroup(G.gens())) + True + """ + return hash((self._ambient_group, self._lifted_gens)) + def _repr_(self): """ Return a string representation @@ -1866,48 +1903,39 @@ def _repr_(self): sage: A._repr_() # needs sage.libs.gap # optional - gap_package_polycyclic 'Multiplicative Abelian subgroup isomorphic to Z generated by {a}' """ - eldv = self._abinvs if self.is_trivial(): return "Trivial Abelian subgroup" s = 'Multiplicative Abelian subgroup isomorphic to ' - s += self._group_notation(eldv) + s += self._group_notation(self.gens_orders()) s += ' generated by ' - s += '{' + ', '.join(map(str, self.gens())) + '}' + s += '{' + ', '.join(map(str, self._lifted_gens)) + '}' return s def gens(self) -> tuple: """ - Return the generators for this subgroup. - - OUTPUT: - - A tuple of group elements generating the subgroup. + Return the generators of ``self`` (as elements in the ambient group). EXAMPLES:: - sage: # needs sage.libs.gap # optional - gap_package_polycyclic - sage: G. = AbelianGroup(2) - sage: A = G.subgroup([a]) - sage: G.gens() - (a, b) - sage: A.gens() - (a,) + sage: G. = AbelianGroup([0,0,6,3]) + sage: H = G.subgroup([a*b, a/b, (a*b)^5, (a*b)^3, c^2, c*d]) + sage: H.gens() + (b^2, a^-1*b^-3*d, c^3, d, c^4*d^2) """ - return self._gens + return self._lifted_gens def gen(self, n): """ - Return the nth generator of this subgroup. + Return the ``n``-th generator of this subgroup. EXAMPLES:: - sage: # needs sage.libs.gap # optional - gap_package_polycyclic - sage: G. = AbelianGroup(2) - sage: A = G.subgroup([a]) - sage: A.gen(0) - a + sage: G. = AbelianGroup([0,0,6,3]) + sage: H = G.subgroup([a*b, a/b, (a*b)^5, (a*b)^3, c^2, c*d]) + sage: [H.gen(n) for n in range(len(H.gens()))] + [b^2, a^-1*b^-3*d, c^3, d, c^4*d^2] """ - return self._gens[n] + return self._lifted_gens[n] # We allow subclasses to override this, analogous to Element