diff --git a/src/doc/en/reference/groups/index.rst b/src/doc/en/reference/groups/index.rst index e3df953c29a..46764ef45e1 100644 --- a/src/doc/en/reference/groups/index.rst +++ b/src/doc/en/reference/groups/index.rst @@ -72,17 +72,24 @@ Matrix and Affine Groups sage/groups/matrix_gps/catalog sage/groups/matrix_gps/matrix_group + sage/groups/matrix_gps/matrix_group_gap sage/groups/matrix_gps/group_element + sage/groups/matrix_gps/group_element_gap sage/groups/matrix_gps/finitely_generated + sage/groups/matrix_gps/finitely_generated_gap sage/groups/matrix_gps/morphism sage/groups/matrix_gps/homset sage/groups/matrix_gps/binary_dihedral sage/groups/matrix_gps/coxeter_group sage/groups/matrix_gps/linear + sage/groups/matrix_gps/linear_gap sage/groups/matrix_gps/orthogonal + sage/groups/matrix_gps/orthogonal_gap sage/groups/matrix_gps/isometries sage/groups/matrix_gps/symplectic + sage/groups/matrix_gps/symplectic_gap sage/groups/matrix_gps/unitary + sage/groups/matrix_gps/unitary_gap sage/groups/matrix_gps/heisenberg sage/groups/affine_gps/affine_group sage/groups/affine_gps/euclidean_group @@ -115,5 +122,6 @@ Internals :maxdepth: 1 sage/groups/matrix_gps/named_group + sage/groups/matrix_gps/named_group_gap .. include:: ../footer.txt diff --git a/src/sage/categories/primer.py b/src/sage/categories/primer.py index 96dff8e2631..7ecc75e711a 100644 --- a/src/sage/categories/primer.py +++ b/src/sage/categories/primer.py @@ -524,12 +524,12 @@ class implements: sage: G = GL(2,ZZ) sage: type(G) - + Here is a piece of the hierarchy of classes above it:: sage: for cls in G.__class__.mro(): print(cls) - + ... diff --git a/src/sage/combinat/root_system/weyl_group.py b/src/sage/combinat/root_system/weyl_group.py index 8ece3dd1a30..6842f0467fa 100644 --- a/src/sage/combinat/root_system/weyl_group.py +++ b/src/sage/combinat/root_system/weyl_group.py @@ -39,8 +39,8 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap -from sage.groups.matrix_gps.group_element import MatrixGroupElement_gap +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.group_element_gap import MatrixGroupElement_gap from sage.groups.perm_gps.permgroup import PermutationGroup_generic from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ diff --git a/src/sage/groups/affine_gps/affine_group.py b/src/sage/groups/affine_gps/affine_group.py index 04f7838bc47..81074a3e50d 100644 --- a/src/sage/groups/affine_gps/affine_group.py +++ b/src/sage/groups/affine_gps/affine_group.py @@ -129,11 +129,11 @@ class AffineGroup(UniqueRepresentation, Group): Some additional ways to create affine groups:: - sage: A = AffineSpace(2, GF(4,'a')); A + sage: A = AffineSpace(2, GF(4,'a')); A # optional - sage.rings.finite_rings Affine Space of dimension 2 over Finite Field in a of size 2^2 - sage: G = AffineGroup(A); G + sage: G = AffineGroup(A); G # optional - sage.rings.finite_rings Affine Group of degree 2 over Finite Field in a of size 2^2 - sage: G is AffineGroup(2,4) # shorthand + sage: G is AffineGroup(2,4) # shorthand # optional - sage.rings.finite_rings True sage: V = ZZ^3; V @@ -152,10 +152,10 @@ def __classcall__(cls, *args, **kwds): EXAMPLES:: - sage: A = AffineSpace(2, GF(4,'a')) - sage: AffineGroup(A) is AffineGroup(2,4) + sage: A = AffineSpace(2, GF(4,'a')) # optional - sage.rings.finite_rings + sage: AffineGroup(A) is AffineGroup(2,4) # optional - sage.rings.finite_rings True - sage: AffineGroup(A) is AffineGroup(2, GF(4,'a')) + sage: AffineGroup(A) is AffineGroup(2, GF(4,'a')) # optional - sage.rings.finite_rings True sage: A = AffineGroup(2, QQ) sage: V = QQ^2 @@ -202,10 +202,10 @@ def __init__(self, degree, ring): TESTS:: - sage: G = AffineGroup(2, GF(5)); G + sage: G = AffineGroup(2, GF(5)); G # optional - sage.rings.finite_rings Affine Group of degree 2 over Finite Field of size 5 - sage: TestSuite(G).run() - sage: G.category() + sage: TestSuite(G).run() # optional - sage.rings.finite_rings + sage: G.category() # optional - sage.rings.finite_rings Category of finite groups sage: Aff6 = AffineGroup(6, QQ) @@ -264,8 +264,8 @@ def _latex_(self): EXAMPLES:: - sage: G = AffineGroup(6, GF(5)) - sage: latex(G) + sage: G = AffineGroup(6, GF(5)) # optional - sage.rings.finite_rings + sage: latex(G) # optional - sage.rings.finite_rings \mathrm{Aff}_{6}(\Bold{F}_{5}) """ return "\\mathrm{Aff}_{%s}(%s)" % (self.degree(), @@ -277,7 +277,7 @@ def _repr_(self): EXAMPLES:: - sage: AffineGroup(6, GF(5)) + sage: AffineGroup(6, GF(5)) # optional - sage.rings.finite_rings Affine Group of degree 6 over Finite Field of size 5 """ return "Affine Group of degree %s over %s" % (self.degree(), @@ -289,7 +289,7 @@ def cardinality(self): EXAMPLES:: - sage: AffineGroup(6, GF(5)).cardinality() + sage: AffineGroup(6, GF(5)).cardinality() # optional - sage.rings.finite_rings 172882428468750000000000000000 sage: AffineGroup(6, ZZ).cardinality() +Infinity @@ -301,17 +301,15 @@ def degree(self): """ Return the dimension of the affine space. - OUTPUT: - - An integer. + OUTPUT: An integer. EXAMPLES:: - sage: G = AffineGroup(6, GF(5)) - sage: g = G.an_element() - sage: G.degree() + sage: G = AffineGroup(6, GF(5)) # optional - sage.rings.finite_rings + sage: g = G.an_element() # optional - sage.rings.finite_rings + sage: G.degree() # optional - sage.rings.finite_rings 6 - sage: G.degree() == g.A().nrows() == g.A().ncols() == g.b().degree() + sage: G.degree() == g.A().nrows() == g.A().ncols() == g.b().degree() # optional - sage.rings.finite_rings True """ return self._degree @@ -329,8 +327,8 @@ def matrix_space(self): EXAMPLES:: - sage: G = AffineGroup(3, GF(5)) - sage: G.matrix_space() + sage: G = AffineGroup(3, GF(5)) # optional - sage.rings.finite_rings + sage: G.matrix_space() # optional - sage.rings.finite_rings Full MatrixSpace of 3 by 3 dense matrices over Finite Field of size 5 """ d = self.degree() @@ -343,8 +341,8 @@ def vector_space(self): EXAMPLES:: - sage: G = AffineGroup(3, GF(5)) - sage: G.vector_space() + sage: G = AffineGroup(3, GF(5)) # optional - sage.rings.finite_rings + sage: G.vector_space() # optional - sage.rings.finite_rings Vector space of dimension 3 over Finite Field of size 5 """ return FreeModule(self.base_ring(), self.degree()) @@ -373,8 +371,8 @@ def linear_space(self): EXAMPLES:: - sage: G = AffineGroup(3, GF(5)) - sage: G.linear_space() + sage: G = AffineGroup(3, GF(5)) # optional - sage.rings.finite_rings + sage: G.linear_space() # optional - sage.rings.finite_rings Full MatrixSpace of 4 by 4 dense matrices over Finite Field of size 5 """ dp = self.degree() + 1 @@ -388,14 +386,12 @@ def linear(self, A): - ``A`` -- anything that determines a matrix - OUTPUT: - - The affine group element `x \mapsto A x`. + OUTPUT: The affine group element `x \mapsto A x`. EXAMPLES:: - sage: G = AffineGroup(3, GF(5)) - sage: G.linear([1,2,3,4,5,6,7,8,0]) + sage: G = AffineGroup(3, GF(5)) # optional - sage.rings.finite_rings + sage: G.linear([1,2,3,4,5,6,7,8,0]) # optional - sage.rings.finite_rings [1 2 3] [0] x |-> [4 0 1] x + [0] [2 3 0] [0] @@ -411,14 +407,12 @@ def translation(self, b): - ``b`` -- anything that determines a vector - OUTPUT: - - The affine group element `x \mapsto x + b`. + OUTPUT: The affine group element `x \mapsto x + b`. EXAMPLES:: - sage: G = AffineGroup(3, GF(5)) - sage: G.translation([1,4,8]) + sage: G = AffineGroup(3, GF(5)) # optional - sage.rings.finite_rings + sage: G.translation([1,4,8]) # optional - sage.rings.finite_rings [1 0 0] [1] x |-> [0 1 0] x + [4] [0 0 1] [3] @@ -446,12 +440,12 @@ def reflection(self, v): EXAMPLES:: - sage: G = AffineGroup(3, QQ) - sage: G.reflection([1,0,0]) + sage: G = AffineGroup(3, QQ) # optional - sage.rings.finite_rings + sage: G.reflection([1,0,0]) # optional - sage.rings.finite_rings [-1 0 0] [0] x |-> [ 0 1 0] x + [0] [ 0 0 1] [0] - sage: G.reflection([3,4,-5]) + sage: G.reflection([3,4,-5]) # optional - sage.rings.finite_rings [ 16/25 -12/25 3/5] [0] x |-> [-12/25 9/25 4/5] x + [0] [ 3/5 4/5 0] [0] @@ -470,13 +464,13 @@ def random_element(self): EXAMPLES:: - sage: G = AffineGroup(4, GF(3)) - sage: G.random_element() # random + sage: G = AffineGroup(4, GF(3)) # optional - sage.rings.finite_rings + sage: G.random_element() # random # optional - sage.rings.finite_rings [2 0 1 2] [1] [2 1 1 2] [2] x |-> [1 0 2 2] x + [2] [1 1 1 1] [2] - sage: G.random_element() in G + sage: G.random_element() in G # optional - sage.rings.finite_rings True """ A = self._GL.random_element() @@ -490,8 +484,8 @@ def _an_element_(self): TESTS:: - sage: G = AffineGroup(4,5) - sage: G.an_element() in G + sage: G = AffineGroup(4,5) # optional - sage.rings.finite_rings + sage: G.an_element() in G # optional - sage.rings.finite_rings True """ A = self._GL.an_element() @@ -504,8 +498,8 @@ def some_elements(self): EXAMPLES:: - sage: G = AffineGroup(4,5) - sage: G.some_elements() + sage: G = AffineGroup(4,5) # optional - sage.rings.finite_rings + sage: G.some_elements() # optional - sage.rings.finite_rings [ [2 0 0 0] [1] [0 1 0 0] [0] x |-> [0 0 1 0] x + [0] @@ -518,7 +512,7 @@ def some_elements(self): [0 1 0 0] [...] x |-> [0 0 1 0] x + [...] [0 0 0 1] [...]] - sage: all(v.parent() is G for v in G.some_elements()) + sage: all(v.parent() is G for v in G.some_elements()) # optional - sage.rings.finite_rings True sage: G = AffineGroup(2,QQ) diff --git a/src/sage/groups/affine_gps/euclidean_group.py b/src/sage/groups/affine_gps/euclidean_group.py index 969dbfe1f81..b88b4f741ac 100644 --- a/src/sage/groups/affine_gps/euclidean_group.py +++ b/src/sage/groups/affine_gps/euclidean_group.py @@ -21,7 +21,7 @@ class EuclideanGroup(AffineGroup): r""" - an Euclidean group. + A Euclidean group. The Euclidean group `E(A)` (or general affine group) of an affine space `A` is the group of all invertible affine transformations from @@ -121,11 +121,11 @@ class EuclideanGroup(AffineGroup): Some additional ways to create Euclidean groups:: - sage: A = AffineSpace(2, GF(4,'a')); A + sage: A = AffineSpace(2, GF(4,'a')); A # optional - sage.rings.finite_rings Affine Space of dimension 2 over Finite Field in a of size 2^2 - sage: G = EuclideanGroup(A); G + sage: G = EuclideanGroup(A); G # optional - sage.rings.finite_rings Euclidean Group of degree 2 over Finite Field in a of size 2^2 - sage: G is EuclideanGroup(2,4) # shorthand + sage: G is EuclideanGroup(2,4) # shorthand # optional - sage.rings.finite_rings True sage: V = ZZ^3; V @@ -144,9 +144,9 @@ class EuclideanGroup(AffineGroup): sage: V = QQ^6 sage: E6 is EuclideanGroup(V) True - sage: G = EuclideanGroup(2, GF(5)); G + sage: G = EuclideanGroup(2, GF(5)); G # optional - sage.rings.finite_rings Euclidean Group of degree 2 over Finite Field of size 5 - sage: TestSuite(G).run() + sage: TestSuite(G).run() # optional - sage.rings.finite_rings REFERENCES: @@ -195,8 +195,8 @@ def _latex_(self): r""" EXAMPLES:: - sage: G = EuclideanGroup(6, GF(5)) - sage: latex(G) + sage: G = EuclideanGroup(6, GF(5)) # optional - sage.rings.finite_rings + sage: latex(G) # optional - sage.rings.finite_rings \mathrm{E}_{6}(\Bold{F}_{5}) """ return "\\mathrm{E}_{%s}(%s)"%(self.degree(), self.base_ring()._latex_()) @@ -207,7 +207,7 @@ def _repr_(self): EXAMPLES:: - sage: EuclideanGroup(6, GF(5)) + sage: EuclideanGroup(6, GF(5)) # optional - sage.rings.finite_rings Euclidean Group of degree 6 over Finite Field of size 5 """ return "Euclidean Group of degree %s over %s"%(self.degree(), self.base_ring()) @@ -218,18 +218,18 @@ def random_element(self): EXAMPLES:: - sage: G = EuclideanGroup(4, GF(3)) - sage: G.random_element() # random + sage: G = EuclideanGroup(4, GF(3)) # optional - sage.rings.finite_rings + sage: G.random_element() # random # optional - sage.rings.finite_rings [2 1 2 1] [1] [1 2 2 1] [0] x |-> [2 2 2 2] x + [1] [1 1 2 2] [2] - sage: G.random_element() in G + sage: G.random_element() in G # optional - sage.rings.finite_rings True TESTS:: - sage: G.random_element().A().is_unitary() + sage: G.random_element().A().is_unitary() # optional - sage.rings.finite_rings True """ while True: diff --git a/src/sage/groups/affine_gps/group_element.py b/src/sage/groups/affine_gps/group_element.py index 127c1d33cb5..48126ba64a8 100644 --- a/src/sage/groups/affine_gps/group_element.py +++ b/src/sage/groups/affine_gps/group_element.py @@ -67,26 +67,24 @@ class AffineGroupElement(MultiplicativeGroupElement): correct vector space. - ``check`` - bool (default: ``True``). Whether to do some - checks or just accept the input as valid. + checks or just accept the input as valid. As a special case, ``A`` can be a matrix obtained from :meth:`matrix`, that is, one row and one column larger. In that case, the group element defining that matrix is reconstructed. - OUTPUT: - - The affine group element `x \mapsto Ax + b` + OUTPUT: The affine group element `x \mapsto Ax + b` EXAMPLES:: - sage: G = AffineGroup(2, GF(3)) - sage: g = G.random_element() - sage: type(g) + sage: G = AffineGroup(2, GF(3)) # optional - sage.rings.finite_rings + sage: g = G.random_element() # optional - sage.rings.finite_rings + sage: type(g) # optional - sage.rings.finite_rings - sage: G(g.matrix()) == g + sage: G(g.matrix()) == g # optional - sage.rings.finite_rings True - sage: G(2) + sage: G(2) # optional - sage.rings.finite_rings [2 0] [0] x |-> [0 2] x + [0] @@ -110,9 +108,9 @@ def __init__(self, parent, A, b=0, convert=True, check=True): TESTS:: - sage: G = AffineGroup(4, GF(5)) - sage: g = G.random_element() - sage: TestSuite(g).run() + sage: G = AffineGroup(4, GF(5)) # optional - sage.rings.finite_rings + sage: g = G.random_element() # optional - sage.rings.finite_rings + sage: TestSuite(g).run() # optional - sage.rings.finite_rings """ try: A = A.matrix() @@ -144,9 +142,7 @@ def A(self): """ Return the general linear part of an affine group element. - OUTPUT: - - The matrix `A` of the affine group element `Ax + b`. + OUTPUT: The matrix `A` of the affine group element `Ax + b`. EXAMPLES:: @@ -163,9 +159,7 @@ def b(self): """ Return the translation part of an affine group element. - OUTPUT: - - The vector `b` of the affine group element `Ax + b`. + OUTPUT: The vector `b` of the affine group element `Ax + b`. EXAMPLES:: @@ -187,29 +181,29 @@ def matrix(self): EXAMPLES:: - sage: G = AffineGroup(3, GF(7)) - sage: g = G([1,2,3,4,5,6,7,8,0], [10,11,12]) - sage: g + sage: G = AffineGroup(3, GF(7)) # optional - sage.rings.finite_rings + sage: g = G([1,2,3,4,5,6,7,8,0], [10,11,12]) # optional - sage.rings.finite_rings + sage: g # optional - sage.rings.finite_rings [1 2 3] [3] x |-> [4 5 6] x + [4] [0 1 0] [5] - sage: g.matrix() + sage: g.matrix() # optional - sage.rings.finite_rings [1 2 3|3] [4 5 6|4] [0 1 0|5] [-----+-] [0 0 0|1] - sage: parent(g.matrix()) + sage: parent(g.matrix()) # optional - sage.rings.finite_rings Full MatrixSpace of 4 by 4 dense matrices over Finite Field of size 7 - sage: g.matrix() == matrix(g) + sage: g.matrix() == matrix(g) # optional - sage.rings.finite_rings True Composition of affine group elements equals multiplication of the matrices:: - sage: g1 = G.random_element() - sage: g2 = G.random_element() - sage: g1.matrix() * g2.matrix() == (g1*g2).matrix() + sage: g1 = G.random_element() # optional - sage.rings.finite_rings + sage: g2 = G.random_element() # optional - sage.rings.finite_rings + sage: g1.matrix() * g2.matrix() == (g1*g2).matrix() # optional - sage.rings.finite_rings True """ A = self._A @@ -338,13 +332,13 @@ def _mul_(self, other): EXAMPLES:: - sage: G = AffineGroup(2, GF(3)) - sage: g = G([1,1, 0,1], [0,1]) - sage: h = G([1,1, 0,1], [1,2]) - sage: g*h + sage: G = AffineGroup(2, GF(3)) # optional - sage.rings.finite_rings + sage: g = G([1,1, 0,1], [0,1]) # optional - sage.rings.finite_rings + sage: h = G([1,1, 0,1], [1,2]) # optional - sage.rings.finite_rings + sage: g*h # optional - sage.rings.finite_rings [1 2] [0] x |-> [0 1] x + [0] - sage: g.matrix() * h.matrix() == (g*h).matrix() + sage: g.matrix() * h.matrix() == (g*h).matrix() # optional - sage.rings.finite_rings True """ parent = self.parent() @@ -361,9 +355,7 @@ def __call__(self, v): - ``v`` -- a polynomial, a multivariate polynomial, a polyhedron, a vector, or anything that can be converted into a vector. - OUTPUT: - - The image of ``v`` under the affine group element. + OUTPUT: The image of ``v`` under the affine group element. EXAMPLES:: @@ -440,16 +432,16 @@ def _act_on_(self, x, self_on_left): EXAMPLES:: - sage: G = AffineGroup(2, GF(3)) - sage: g = G([1,2,3,4], [5,6]) - sage: g + sage: G = AffineGroup(2, GF(3)) # optional - sage.rings.finite_rings + sage: g = G([1,2,3,4], [5,6]) # optional - sage.rings.finite_rings + sage: g # optional - sage.rings.finite_rings [1 2] [2] x |-> [0 1] x + [0] - sage: v = vector(GF(3), [1,-1]); v + sage: v = vector(GF(3), [1,-1]); v # optional - sage.rings.finite_rings (1, 2) - sage: g*v + sage: g*v # optional - sage.rings.finite_rings (1, 2) - sage: g*v == g.A() * v + g.b() + sage: g*v == g.A() * v + g.b() # optional - sage.rings.finite_rings True """ if self_on_left: @@ -459,24 +451,22 @@ def __invert__(self): """ Return the inverse group element. - OUTPUT: - - Another affine group element. + OUTPUT: Another affine group element. EXAMPLES:: - sage: G = AffineGroup(2, GF(3)) - sage: g = G([1,2,3,4], [5,6]) - sage: g + sage: G = AffineGroup(2, GF(3)) # optional - sage.rings.finite_rings + sage: g = G([1,2,3,4], [5,6]) # optional - sage.rings.finite_rings + sage: g # optional - sage.rings.finite_rings [1 2] [2] x |-> [0 1] x + [0] - sage: ~g + sage: ~g # optional - sage.rings.finite_rings [1 1] [1] x |-> [0 1] x + [0] - sage: g * g.inverse() # indirect doctest + sage: g * g.inverse() # indirect doctest # optional - sage.rings.finite_rings [1 0] [0] x |-> [0 1] x + [0] - sage: g * g.inverse() == g.inverse() * g == G(1) + sage: g * g.inverse() == g.inverse() * g == G(1) # optional - sage.rings.finite_rings True """ parent = self.parent() @@ -488,9 +478,7 @@ def _richcmp_(self, other, op): """ Compare ``self`` with ``other``. - OUTPUT: - - boolean + OUTPUT: boolean EXAMPLES:: diff --git a/src/sage/groups/group.pyx b/src/sage/groups/group.pyx index b829bbf68bf..7947f0f876d 100644 --- a/src/sage/groups/group.pyx +++ b/src/sage/groups/group.pyx @@ -143,7 +143,7 @@ cdef class Group(Parent): EXAMPLES:: - sage: SL(2, 7).is_commutative() + sage: SL(2, 7).is_commutative() # optional - sage.rings.finite_rings False """ return self.is_abelian() @@ -215,8 +215,8 @@ cdef class Group(Parent): EXAMPLES:: - sage: G = AbelianGroup([2,3,4,5]) - sage: G.an_element() + sage: G = AbelianGroup([2,3,4,5]) # optional - sage.groups + sage: G.an_element() # optional - sage.groups f0*f1*f2*f3 """ from sage.misc.misc_c import prod diff --git a/src/sage/groups/matrix_gps/binary_dihedral.py b/src/sage/groups/matrix_gps/binary_dihedral.py index 0edae3511c2..c8813eeabd4 100644 --- a/src/sage/groups/matrix_gps/binary_dihedral.py +++ b/src/sage/groups/matrix_gps/binary_dihedral.py @@ -16,7 +16,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap from sage.structure.unique_representation import UniqueRepresentation from sage.rings.number_field.number_field import CyclotomicField from sage.matrix.matrix_space import MatrixSpace diff --git a/src/sage/groups/matrix_gps/coxeter_group.py b/src/sage/groups/matrix_gps/coxeter_group.py index 607eda9519a..66a877c61ac 100644 --- a/src/sage/groups/matrix_gps/coxeter_group.py +++ b/src/sage/groups/matrix_gps/coxeter_group.py @@ -186,10 +186,12 @@ class CoxeterMatrixGroup(UniqueRepresentation, FinitelyGeneratedMatrixGroup_gene [2 2 3 2 1] sage: W = CoxeterGroup(['H',3], implementation="reflection") sage: W - Finite Coxeter group over Number Field in a with defining polynomial x^2 - 5 with a = 2.236067977499790? with Coxeter matrix: - [1 3 2] - [3 1 5] - [2 5 1] + Finite Coxeter group over + Number Field in a with defining polynomial x^2 - 5 with a = 2.236067977499790? + with Coxeter matrix: + [1 3 2] + [3 1 5] + [2 5 1] """ @staticmethod def __classcall_private__(cls, data, base_ring=None, index_set=None): @@ -834,7 +836,7 @@ def action_on_root_indices(self, i, side="left"): """ Return the action on the set of roots. - The roots are ordered as in the output of the method `roots`. + The roots are ordered as in the output of the method :meth:`roots`. EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/finitely_generated.py b/src/sage/groups/matrix_gps/finitely_generated.py index 0b5f14014ee..874b6b72d7b 100644 --- a/src/sage/groups/matrix_gps/finitely_generated.py +++ b/src/sage/groups/matrix_gps/finitely_generated.py @@ -6,10 +6,10 @@ EXAMPLES:: - sage: F = GF(3) - sage: gens = [matrix(F,2, [1,0, -1,1]), matrix(F,2, [1,1,0,1])] - sage: G = MatrixGroup(gens) - sage: G.conjugacy_classes_representatives() + sage: F = GF(3) # optional - sage.rings.finite_rings + sage: gens = [matrix(F, 2, [1,0, -1,1]), matrix(F, 2, [1,1,0,1])] # optional - sage.rings.finite_rings + sage: G = MatrixGroup(gens) # optional - sage.rings.finite_rings + sage: G.conjugacy_classes_representatives() # optional - sage.rings.finite_rings ( [1 0] [0 2] [0 1] [2 0] [0 2] [0 1] [0 2] [0 1], [1 1], [2 1], [0 2], [1 2], [2 2], [1 0] @@ -18,7 +18,7 @@ The finitely generated matrix groups can also be constructed as subgroups of matrix groups:: - sage: SL2Z = SL(2,ZZ) + sage: SL2Z = SL(2, ZZ) sage: S, T = SL2Z.gens() sage: SL2Z.subgroup([T^2]) Subgroup with 1 generators ( @@ -51,35 +51,32 @@ - Sebastian Oehms (2019-01): Revision of :trac:`25706` (:trac:`26903` and :trac:`27143`). """ -############################################################################## +# ############################################################################# # Copyright (C) 2006 David Joyner and William Stein -# Copyright (C) 2013 Volker Braun +# 2009 Mike Hansen +# 2012 Rob Beezer +# 2013 Volker Braun +# 2013 Nathann Cohen +# 2013 Travis Scrimshaw +# 2017 Peter Bruin +# 2021 Frédéric Chapoton +# 2023 Matthias Koeppe # # Distributed under the terms of the GNU General Public License (GPL) # # The full text of the GPL is available at: # # https://www.gnu.org/licenses/ -############################################################################## +# ############################################################################# +from sage.groups.matrix_gps.group_element import is_MatrixGroupElement +from sage.groups.matrix_gps.matrix_group import MatrixGroup_generic +from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import is_MatrixSpace +from sage.misc.cachefunc import cached_method from sage.rings.integer_ring import ZZ -from sage.rings.qqbar import QQbar from sage.structure.element import is_Matrix -from sage.matrix.matrix_space import MatrixSpace, is_MatrixSpace -from sage.matrix.constructor import matrix from sage.structure.sequence import Sequence -from sage.misc.cachefunc import cached_method -from sage.modules.free_module_element import vector -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.power_series_ring import PowerSeriesRing -from sage.rings.fraction_field import FractionField -from sage.misc.functional import cyclotomic_polynomial -from sage.rings.number_field.number_field import CyclotomicField -from sage.combinat.integer_vector import IntegerVectors - -from sage.groups.matrix_gps.matrix_group import (MatrixGroup_generic, - MatrixGroup_gap) -from sage.groups.matrix_gps.group_element import is_MatrixGroupElement def normalize_square_matrices(matrices): @@ -93,9 +90,9 @@ def normalize_square_matrices(matrices): EXAMPLES:: sage: from sage.groups.matrix_gps.finitely_generated import normalize_square_matrices - sage: m1 = [[1,2],[3,4]] + sage: m1 = [[1,2], [3,4]] sage: m2 = [2, 3, 4, 5] - sage: m3 = matrix(QQ, [[1/2,1/3],[1/4,1/5]]) + sage: m3 = matrix(QQ, [[1/2,1/3], [1/4,1/5]]) sage: m4 = MatrixGroup(m3).gen(0) sage: normalize_square_matrices([m1, m2, m3, m4]) [ @@ -144,7 +141,7 @@ def normalize_square_matrices(matrices): def QuaternionMatrixGroupGF3(): r""" - The quaternion group as a set of `2\times 2` matrices over `GF(3)`. + The quaternion group as a set of `2\times 2` matrices over `\GF{3}`. OUTPUT: @@ -164,37 +161,37 @@ def QuaternionMatrixGroupGF3(): is the product of `I` and `J`. :: sage: from sage.groups.matrix_gps.finitely_generated import QuaternionMatrixGroupGF3 - sage: Q = QuaternionMatrixGroupGF3() - sage: Q.order() + sage: Q = QuaternionMatrixGroupGF3() # optional - sage.rings.finite_rings + sage: Q.order() # optional - sage.rings.finite_rings 8 - sage: aye = Q.gens()[0]; aye + sage: aye = Q.gens()[0]; aye # optional - sage.rings.finite_rings [1 1] [1 2] - sage: jay = Q.gens()[1]; jay + sage: jay = Q.gens()[1]; jay # optional - sage.rings.finite_rings [2 1] [1 1] - sage: kay = aye*jay; kay + sage: kay = aye*jay; kay # optional - sage.rings.finite_rings [0 2] [1 0] TESTS:: - sage: groups.matrix.QuaternionGF3() + sage: groups.matrix.QuaternionGF3() # optional - sage.rings.finite_rings Matrix group over Finite Field of size 3 with 2 generators ( [1 1] [2 1] [1 2], [1 1] ) - sage: Q = QuaternionMatrixGroupGF3() - sage: QP = Q.as_permutation_group() - sage: QP.is_isomorphic(QuaternionGroup()) + sage: Q = QuaternionMatrixGroupGF3() # optional - sage.rings.finite_rings + sage: QP = Q.as_permutation_group() # optional - sage.rings.finite_rings + sage: QP.is_isomorphic(QuaternionGroup()) # optional - sage.rings.finite_rings True - sage: H = DihedralGroup(4) - sage: H.order() + sage: H = DihedralGroup(4) # optional - sage.groups sage.rings.finite_rings + sage: H.order() # optional - sage.groups sage.rings.finite_rings 8 - sage: QP.is_abelian(), H.is_abelian() + sage: QP.is_abelian(), H.is_abelian() # optional - sage.groups sage.rings.finite_rings (False, False) - sage: QP.is_isomorphic(H) + sage: QP.is_isomorphic(H) # optional - sage.groups sage.rings.finite_rings False """ from sage.rings.finite_rings.finite_field_constructor import FiniteField @@ -219,9 +216,9 @@ def MatrixGroup(*gens, **kwds): EXAMPLES:: - sage: F = GF(5) - sage: gens = [matrix(F,2,[1,2, -1, 1]), matrix(F,2, [1,1, 0,1])] - sage: G = MatrixGroup(gens); G + sage: F = GF(5) # optional - sage.rings.finite_rings + sage: gens = [matrix(F, 2, [1,2, -1,1]), matrix(F,2, [1,1, 0,1])] # optional - sage.rings.finite_rings + sage: G = MatrixGroup(gens); G # optional - sage.rings.finite_rings Matrix group over Finite Field of size 5 with 2 generators ( [1 2] [1 1] [4 1], [0 1] @@ -233,8 +230,8 @@ def MatrixGroup(*gens, **kwds): matrices over the finite field, so creates that matrix group there:: - sage: gens = [matrix(2,[1,2, -1, 1]), matrix(GF(7), 2, [1,1, 0,1]), 2] - sage: G = MatrixGroup(gens); G + sage: gens = [matrix(2, [1,2, -1,1]), matrix(GF(7), 2, [1,1, 0,1]), 2] # optional - sage.rings.finite_rings + sage: G = MatrixGroup(gens); G # optional - sage.rings.finite_rings Matrix group over Finite Field of size 7 with 3 generators ( [1 2] [1 1] [2 0] [6 1], [0 1], [0 2] @@ -242,17 +239,17 @@ def MatrixGroup(*gens, **kwds): Each generator must be invertible:: - sage: G = MatrixGroup([matrix(ZZ,2,[1,2,3,4])]) + sage: G = MatrixGroup([matrix(ZZ, 2, [1,2,3,4])]) Traceback (most recent call last): ... ValueError: each generator must be an invertible matrix - sage: F = GF(5); MS = MatrixSpace(F,2,2) - sage: MatrixGroup([MS.0]) + sage: F = GF(5); MS = MatrixSpace(F, 2, 2) # optional - sage.rings.finite_rings + sage: MatrixGroup([MS.0]) # optional - sage.rings.finite_rings Traceback (most recent call last): ... ValueError: each generator must be an invertible matrix - sage: MatrixGroup([MS.0], check=False) # works formally but is mathematical nonsense + sage: MatrixGroup([MS.0], check=False) # works formally but is mathematical nonsense # optional - sage.rings.finite_rings Matrix group over Finite Field of size 5 with 1 generators ( [1 0] [0 0] @@ -293,16 +290,23 @@ def MatrixGroup(*gens, **kwds): MS = gens.universe() base_ring = MS.base_ring() degree = ZZ(MS.ncols()) # == MS.nrows() - from sage.libs.gap.libgap import libgap category = kwds.get('category', None) try: - gap_gens = [libgap(matrix_gen) for matrix_gen in gens] - gap_group = libgap.Group(gap_gens) - return FinitelyGeneratedMatrixGroup_gap(degree, base_ring, gap_group, - category=category) - except (TypeError, ValueError): - return FinitelyGeneratedMatrixGroup_generic(degree, base_ring, gens, + from sage.libs.gap.libgap import libgap + from .finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap + except ImportError: + pass + else: + try: + gap_gens = [libgap(matrix_gen) for matrix_gen in gens] + gap_group = libgap.Group(gap_gens) + return FinitelyGeneratedMatrixGroup_gap(degree, base_ring, gap_group, category=category) + except (TypeError, ValueError): + pass + + return FinitelyGeneratedMatrixGroup_generic(degree, base_ring, gens, + category=category) ################################################################### # @@ -315,19 +319,19 @@ class FinitelyGeneratedMatrixGroup_generic(MatrixGroup_generic): """ TESTS:: - sage: m1 = matrix(SR, [[1,2],[3,4]]) - sage: m2 = matrix(SR, [[1,3],[-1,0]]) - sage: MatrixGroup(m1) == MatrixGroup(m1) + sage: m1 = matrix(SR, [[1,2], [3,4]]) # optional - sage.symbolic + sage: m2 = matrix(SR, [[1,3], [-1,0]]) # optional - sage.symbolic + sage: MatrixGroup(m1) == MatrixGroup(m1) # optional - sage.symbolic True - sage: MatrixGroup(m1) == MatrixGroup(m1.change_ring(QQ)) + sage: MatrixGroup(m1) == MatrixGroup(m1.change_ring(QQ)) # optional - sage.symbolic False - sage: MatrixGroup(m1) == MatrixGroup(m2) + sage: MatrixGroup(m1) == MatrixGroup(m2) # optional - sage.symbolic False - sage: MatrixGroup(m1, m2) == MatrixGroup(m2, m1) + sage: MatrixGroup(m1, m2) == MatrixGroup(m2, m1) # optional - sage.symbolic False - sage: m1 = matrix(QQ, [[1,2],[3,4]]) - sage: m2 = matrix(QQ, [[1,3],[-1,0]]) + sage: m1 = matrix(QQ, [[1,2], [3,4]]) + sage: m2 = matrix(QQ, [[1,3], [-1,0]]) sage: MatrixGroup(m1) == MatrixGroup(m1) True sage: MatrixGroup(m1) == MatrixGroup(m2) @@ -335,9 +339,9 @@ class FinitelyGeneratedMatrixGroup_generic(MatrixGroup_generic): sage: MatrixGroup(m1, m2) == MatrixGroup(m2, m1) False - sage: G = GL(2, GF(3)) - sage: H = G.as_matrix_group() - sage: H == G, G == H + sage: G = GL(2, GF(3)) # optional - sage.rings.finite_rings + sage: H = G.as_matrix_group() # optional - sage.rings.finite_rings + sage: H == G, G == H # optional - sage.rings.finite_rings (True, True) """ @@ -347,16 +351,16 @@ def __init__(self, degree, base_ring, generator_matrices, category=None): EXAMPLES:: - sage: m1 = matrix(SR, [[1,2],[3,4]]) - sage: m2 = matrix(SR, [[1,3],[-1,0]]) - sage: G = MatrixGroup(m1, m2) - sage: TestSuite(G).run() - sage: type(G) + sage: m1 = matrix(SR, [[1,2], [3,4]]) # optional - sage.symbolic + sage: m2 = matrix(SR, [[1,3], [-1,0]]) # optional - sage.symbolic + sage: G = MatrixGroup(m1, m2) # optional - sage.symbolic + sage: TestSuite(G).run() # optional - sage.symbolic + sage: type(G) # optional - sage.symbolic sage: from sage.groups.matrix_gps.finitely_generated import \ ....: FinitelyGeneratedMatrixGroup_generic - sage: G = FinitelyGeneratedMatrixGroup_generic(2, QQ, [matrix(QQ,[[1,2],[3,4]])]) + sage: G = FinitelyGeneratedMatrixGroup_generic(2, QQ, [matrix(QQ, [[1,2], [3,4]])]) sage: G.gens() ( [1 2] @@ -373,24 +377,24 @@ def gens(self): EXAMPLES:: - sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]), MS([[1,1],[0,1]])] - sage: G = MatrixGroup(gens) - sage: gens[0] in G + sage: F = GF(3); MS = MatrixSpace(F, 2, 2) # optional - sage.rings.finite_rings + sage: gens = [MS([[1,0], [0,1]]), MS([[1,1], [0,1]])] # optional - sage.rings.finite_rings + sage: G = MatrixGroup(gens) # optional - sage.rings.finite_rings + sage: gens[0] in G # optional - sage.rings.finite_rings True - sage: gens = G.gens() - sage: gens[0] in G + sage: gens = G.gens() # optional - sage.rings.finite_rings + sage: gens[0] in G # optional - sage.rings.finite_rings True - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] + sage: gens = [MS([[1,0], [0,1]]), MS([[1,1], [0,1]])] # optional - sage.rings.finite_rings - sage: F = GF(5); MS = MatrixSpace(F,2,2) - sage: G = MatrixGroup([MS(1), MS([1,2,3,4])]) - sage: G + sage: F = GF(5); MS = MatrixSpace(F, 2, 2) # optional - sage.rings.finite_rings + sage: G = MatrixGroup([MS(1), MS([1,2, 3,4])]) # optional - sage.rings.finite_rings + sage: G # optional - sage.rings.finite_rings Matrix group over Finite Field of size 5 with 2 generators ( [1 0] [1 2] [0 1], [3 4] ) - sage: G.gens() + sage: G.gens() # optional - sage.rings.finite_rings ( [1 0] [1 2] [0 1], [3 4] @@ -409,13 +413,13 @@ def gen(self, i): EXAMPLES:: - sage: H = GL(2, GF(3)) - sage: h1, h2 = H([[1,0],[2,1]]), H([[1,1],[0,1]]) - sage: G = H.subgroup([h1, h2]) - sage: G.gen(0) + sage: H = GL(2, GF(3)) # optional - sage.rings.finite_rings + sage: h1, h2 = H([[1,0], [2,1]]), H([[1,1], [0,1]]) # optional - sage.rings.finite_rings + sage: G = H.subgroup([h1, h2]) # optional - sage.rings.finite_rings + sage: G.gen(0) # optional - sage.rings.finite_rings [1 0] [2 1] - sage: G.gen(0).matrix() == h1.matrix() + sage: G.gen(0).matrix() == h1.matrix() # optional - sage.rings.finite_rings True """ return self.gens()[i] @@ -430,10 +434,10 @@ def ngens(self): EXAMPLES:: - sage: H = GL(2, GF(3)) - sage: h1, h2 = H([[1,0],[2,1]]), H([[1,1],[0,1]]) - sage: G = H.subgroup([h1, h2]) - sage: G.ngens() + sage: H = GL(2, GF(3)) # optional - sage.rings.finite_rings + sage: h1, h2 = H([[1,0], [2,1]]), H([[1,1], [0,1]]) # optional - sage.rings.finite_rings + sage: G = H.subgroup([h1, h2]) # optional - sage.rings.finite_rings + sage: G.ngens() # optional - sage.rings.finite_rings 2 """ return len(self._gens_matrix) @@ -444,17 +448,17 @@ def __reduce__(self): TESTS:: - sage: G = MatrixGroup([matrix(CC, [[1,2],[3,4]]), - ....: matrix(CC, [[1,3],[-1,0]])]) + sage: G = MatrixGroup([matrix(CC, [[1,2], [3,4]]), + ....: matrix(CC, [[1,3], [-1,0]])]) sage: loads(dumps(G)) == G True Check that :trac:`22128` is fixed:: - sage: R = MatrixSpace(SR, 2) - sage: G = MatrixGroup([R([[1, 1], [0, 1]])]) - sage: G.register_embedding(R) - sage: loads(dumps(G)) + sage: R = MatrixSpace(SR, 2) # optional - sage.symbolic + sage: G = MatrixGroup([R([[1, 1], [0, 1]])]) # optional - sage.symbolic + sage: G.register_embedding(R) # optional - sage.symbolic + sage: loads(dumps(G)) # optional - sage.symbolic Matrix group over Symbolic Ring with 1 generators ( [1 1] [0 1] @@ -466,874 +470,11 @@ def _test_matrix_generators(self, **options): """ EXAMPLES:: - sage: m1 = matrix(SR, [[1,2],[3,4]]) - sage: m2 = matrix(SR, [[1,3],[-1,0]]) - sage: G = MatrixGroup(m1, m2) - sage: G._test_matrix_generators() + sage: m1 = matrix(SR, [[1,2], [3,4]]) # optional - sage.symbolic + sage: m2 = matrix(SR, [[1,3], [-1,0]]) # optional - sage.symbolic + sage: G = MatrixGroup(m1, m2) # optional - sage.symbolic + sage: G._test_matrix_generators() # optional - sage.symbolic """ tester = self._tester(**options) for g,h in zip(self.gens(), MatrixGroup(self.gens()).gens()): tester.assertEqual(g.matrix(), h.matrix()) - -################################################################### -# -# Matrix group over a ring that GAP understands -# -################################################################### - - -class FinitelyGeneratedMatrixGroup_gap(MatrixGroup_gap): - """ - Matrix group generated by a finite number of matrices. - - EXAMPLES:: - - sage: m1 = matrix(GF(11), [[1,2],[3,4]]) - sage: m2 = matrix(GF(11), [[1,3],[10,0]]) - sage: G = MatrixGroup(m1, m2); G - Matrix group over Finite Field of size 11 with 2 generators ( - [1 2] [ 1 3] - [3 4], [10 0] - ) - sage: type(G) - - sage: TestSuite(G).run() - """ - - def __reduce__(self): - """ - Implement pickling. - - EXAMPLES:: - - sage: m1 = matrix(QQ, [[1,2],[3,4]]) - sage: m2 = matrix(QQ, [[1,3],[-1,0]]) - sage: loads(MatrixGroup(m1, m2).dumps()) - Matrix group over Rational Field with 2 generators ( - [1 2] [ 1 3] - [3 4], [-1 0] - ) - """ - return (MatrixGroup, - tuple(g.matrix() for g in self.gens()) + ({'check':False},)) - - def as_permutation_group(self, algorithm=None, seed=None): - r""" - Return a permutation group representation for the group. - - In most cases occurring in practice, this is a permutation - group of minimal degree (the degree being determined from - orbits under the group action). When these orbits are hard to - compute, the procedure can be time-consuming and the degree - may not be minimal. - - INPUT: - - - ``algorithm`` -- ``None`` or ``'smaller'``. In the latter - case, try harder to find a permutation representation of - small degree. - - ``seed`` -- ``None`` or an integer specifying the seed - to fix results depending on pseudo-random-numbers. Here - it makes sense to be used with respect to the ``'smaller'`` - option, since gap produces random output in that context. - - OUTPUT: - - A permutation group isomorphic to ``self``. The - ``algorithm='smaller'`` option tries to return an isomorphic - group of low degree, but is not guaranteed to find the - smallest one and must not even differ from the one obtained - without the option. In that case repeating the invocation - may help (see the example below). - - EXAMPLES:: - - sage: MS = MatrixSpace(GF(2), 5, 5) - sage: A = MS([[0,0,0,0,1],[0,0,0,1,0],[0,0,1,0,0],[0,1,0,0,0],[1,0,0,0,0]]) - sage: G = MatrixGroup([A]) - sage: G.as_permutation_group().order() - 2 - - A finite subgroup of GL(12,Z) as a permutation group:: - - sage: imf = libgap.function_factory('ImfMatrixGroup') - sage: GG = imf( 12, 3 ) - sage: G = MatrixGroup(GG.GeneratorsOfGroup()) - sage: G.cardinality() - 21499084800 - sage: P = G.as_permutation_group() - sage: Psmaller = G.as_permutation_group(algorithm="smaller", seed=6) - sage: P == Psmaller - False - sage: P.cardinality() - 21499084800 - sage: P.degree() - 144 - sage: Psmaller.cardinality() - 21499084800 - sage: Psmaller.degree() - 80 - - .. NOTE:: - - In this case, the "smaller" option returned an isomorphic - group of lower degree. The above example used GAP's library - of irreducible maximal finite ("imf") integer matrix groups - to construct the MatrixGroup G over GF(7). The section - "Irreducible Maximal Finite Integral Matrix Groups" in the - GAP reference manual has more details. - - .. NOTE:: - - Concerning the option ``algorithm='smaller'`` you should note - the following from GAP documentation: "The methods used might - involve the use of random elements and the permutation - representation (or even the degree of the representation) is - not guaranteed to be the same for different calls of - SmallerDegreePermutationRepresentation." - - To obtain a reproducible result the optional argument ``seed`` - may be used as in the example above. - - TESTS:: - - sage: A = matrix(QQ, 2, [0, 1, 1, 0]) - sage: B = matrix(QQ, 2, [1, 0, 0, 1]) - sage: a, b = MatrixGroup([A, B]).as_permutation_group().gens() - sage: a.order(), b.order() - (2, 1) - - The above example in GL(12,Z), reduced modulo 7:: - - sage: MS = MatrixSpace(GF(7), 12, 12) - sage: G = MatrixGroup([MS(g) for g in GG.GeneratorsOfGroup()]) - sage: G.cardinality() - 21499084800 - sage: P = G.as_permutation_group() - sage: P.cardinality() - 21499084800 - - Check that large degree is still working:: - - sage: Sp(6,3).as_permutation_group().cardinality() - 9170703360 - - Check that :trac:`25706` still works after :trac:`26903`:: - - sage: MG = GU(3,2).as_matrix_group() - sage: PG = MG.as_permutation_group() - sage: mg = MG.an_element() - sage: PG(mg).order() # particular element depends on the set of GAP packages installed - 6 - """ - # Note that the output of IsomorphismPermGroup() depends on - # memory locations and will change if you change the order of - # doctests and/or architecture - from sage.groups.perm_gps.permgroup import PermutationGroup - if not self.is_finite(): - raise NotImplementedError("group must be finite") - if seed is not None: - from sage.libs.gap.libgap import libgap - libgap.set_seed(ZZ(seed)) - iso = self._libgap_().IsomorphismPermGroup() - if algorithm == "smaller": - iso = iso.Image().SmallerDegreePermutationRepresentation() - return PermutationGroup(iso.Image().GeneratorsOfGroup().sage(), - canonicalize=False) - - def module_composition_factors(self, algorithm=None): - r""" - Return a list of triples consisting of [base field, dimension, - irreducibility], for each of the Meataxe composition factors - modules. The ``algorithm="verbose"`` option returns more information, - but in Meataxe notation. - - EXAMPLES:: - - sage: F = GF(3); MS = MatrixSpace(F,4,4) - sage: M = MS(0) - sage: M[0,1]=1;M[1,2]=1;M[2,3]=1;M[3,0]=1 - sage: G = MatrixGroup([M]) - sage: G.module_composition_factors() - [(Finite Field of size 3, 1, True), - (Finite Field of size 3, 1, True), - (Finite Field of size 3, 2, True)] - sage: F = GF(7); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[0,1],[-1,0]]),MS([[1,1],[2,3]])] - sage: G = MatrixGroup(gens) - sage: G.module_composition_factors() - [(Finite Field of size 7, 2, True)] - - Type ``G.module_composition_factors(algorithm='verbose')`` to get a - more verbose version. - - For more on MeatAxe notation, see - https://www.gap-system.org/Manuals/doc/ref/chap69.html - """ - from sage.libs.gap.libgap import libgap - F = self.base_ring() - if not F.is_finite(): - raise NotImplementedError("base ring must be finite") - n = self.degree() - MS = MatrixSpace(F, n, n) - mats = [MS(g.matrix()) for g in self.gens()] - # initializing list of mats by which the gens act on self - mats_gap = libgap(mats) - M = mats_gap.GModuleByMats(F) - compo = libgap.function_factory('MTX.CompositionFactors') - MCFs = compo(M) - if algorithm == "verbose": - print(str(MCFs) + "\n") - return sorted((MCF['field'].sage(), - MCF['dimension'].sage(), - MCF['IsIrreducible'].sage()) for MCF in MCFs) - - def invariant_generators(self): - r""" - Return invariant ring generators. - - Computes generators for the polynomial ring - `F[x_1,\ldots,x_n]^G`, where `G` in `GL(n,F)` is a finite matrix - group. - - In the "good characteristic" case the polynomials returned - form a minimal generating set for the algebra of `G`-invariant - polynomials. In the "bad" case, the polynomials returned - are primary and secondary invariants, forming a not - necessarily minimal generating set for the algebra of - `G`-invariant polynomials. - - ALGORITHM: - - Wraps Singular's ``invariant_algebra_reynolds`` and ``invariant_ring`` - in ``finvar.lib``. - - EXAMPLES:: - - sage: F = GF(7); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[0,1],[-1,0]]),MS([[1,1],[2,3]])] - sage: G = MatrixGroup(gens) - sage: G.invariant_generators() - [x1^7*x2 - x1*x2^7, - x1^12 - 2*x1^9*x2^3 - x1^6*x2^6 + 2*x1^3*x2^9 + x2^12, - x1^18 + 2*x1^15*x2^3 + 3*x1^12*x2^6 + 3*x1^6*x2^12 - 2*x1^3*x2^15 + x2^18] - - sage: q = 4; a = 2 - sage: MS = MatrixSpace(QQ, 2, 2) - sage: gen1 = [[1/a,(q-1)/a],[1/a, -1/a]]; gen2 = [[1,0],[0,-1]]; gen3 = [[-1,0],[0,1]] - sage: G = MatrixGroup([MS(gen1),MS(gen2),MS(gen3)]) - sage: G.cardinality() - 12 - sage: G.invariant_generators() - [x1^2 + 3*x2^2, x1^6 + 15*x1^4*x2^2 + 15*x1^2*x2^4 + 33*x2^6] - - sage: F = CyclotomicField(8) - sage: z = F.gen() - sage: a = z+1/z - sage: b = z^2 - sage: MS = MatrixSpace(F,2,2) - sage: g1 = MS([[1/a, 1/a], [1/a, -1/a]]) - sage: g2 = MS([[-b, 0], [0, b]]) - sage: G = MatrixGroup([g1,g2]) - sage: G.invariant_generators() - [x1^4 + 2*x1^2*x2^2 + x2^4, - x1^5*x2 - x1*x2^5, - x1^8 + 28/9*x1^6*x2^2 + 70/9*x1^4*x2^4 + 28/9*x1^2*x2^6 + x2^8] - - AUTHORS: - - - David Joyner, Simon King and Martin Albrecht. - - REFERENCES: - - - Singular reference manual - - - [Stu1993]_ - - - S. King, "Minimal Generating Sets of non-modular invariant - rings of finite groups", :arxiv:`math/0703035`. - """ - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - from sage.interfaces.singular import singular - gens = self.gens() - singular.LIB("finvar.lib") - n = self.degree() # len((gens[0].matrix()).rows()) - F = self.base_ring() - q = F.characteristic() - # test if the field is admissible - if F.gen() == 1: # we got the rationals or GF(prime) - FieldStr = str(F.characteristic()) - elif hasattr(F,'polynomial'): # we got an algebraic extension - if len(F.gens()) > 1: - raise NotImplementedError("can only deal with finite fields and (simple algebraic extensions of) the rationals") - FieldStr = '(%d,%s)' % (F.characteristic(), str(F.gen())) - else: # we have a transcendental extension - FieldStr = '(%d,%s)' % (F.characteristic(), - ','.join(str(p) for p in F.gens())) - - # Setting Singular's variable names - # We need to make sure that field generator and variables get different names. - if str(F.gen())[0] == 'x': - VarStr = 'y' - else: - VarStr = 'x' - VarNames = '(' + ','.join((VarStr+str(i) for i in range(1, n+1)))+')' - # The function call and affectation below have side-effects. Do not remove! - # (even if pyflakes say so) - R = singular.ring(FieldStr, VarNames, 'dp') - if hasattr(F, 'polynomial') and F.gen() != 1: - # we have to define minpoly - singular.eval('minpoly = '+str(F.polynomial()).replace('x',str(F.gen()))) - A = [singular.matrix(n,n,str((x.matrix()).list())) for x in gens] - Lgens = ','.join((x.name() for x in A)) - PR = PolynomialRing(F, n, [VarStr+str(i) for i in range(1,n+1)]) - - if q == 0 or (q > 0 and self.cardinality() % q): - from sage.matrix.constructor import Matrix - try: - elements = [g.matrix() for g in self.list()] - except (TypeError, ValueError): - elements - if elements is not None: - ReyName = 't'+singular._next_var_name() - singular.eval('matrix %s[%d][%d]' % (ReyName, - self.cardinality(), n)) - for i in range(1,self.cardinality()+1): - M = Matrix(F, elements[i-1]) - D = [{} for foobar in range(self.degree())] - for x,y in M.dict().items(): - D[x[0]][x[1]] = y - for row in range(self.degree()): - for t in D[row].items(): - singular.eval('%s[%d,%d]=%s[%d,%d]+(%s)*var(%d)' - % (ReyName,i,row+1,ReyName,i,row+1, repr(t[1]),t[0]+1)) - IRName = 't'+singular._next_var_name() - singular.eval('matrix %s = invariant_algebra_reynolds(%s)' % (IRName,ReyName)) - else: - ReyName = 't'+singular._next_var_name() - singular.eval('list %s=group_reynolds((%s))' % (ReyName, Lgens)) - IRName = 't'+singular._next_var_name() - singular.eval('matrix %s = invariant_algebra_reynolds(%s[1])' % (IRName, ReyName)) - - OUT = [singular.eval(IRName+'[1,%d]' % (j)) - for j in range(1, 1+int(singular('ncols('+IRName+')')))] - return [PR(gen) for gen in OUT] - if self.cardinality() % q == 0: - PName = 't' + singular._next_var_name() - SName = 't' + singular._next_var_name() - singular.eval('matrix %s,%s=invariant_ring(%s)' % (PName, SName, Lgens)) - OUT = [singular.eval(PName+'[1,%d]' % (j)) - for j in range(1,1+singular('ncols('+PName+')'))] - OUT += [singular.eval(SName+'[1,%d]' % (j)) - for j in range(2,1+singular('ncols('+SName+')'))] - return [PR(gen) for gen in OUT] - - def molien_series(self, chi=None, return_series=True, prec=20, variable='t'): - r""" - Compute the Molien series of this finite group with respect to the - character ``chi``. - - It can be returned either as a rational function in one variable - or a power series in one variable. The base field must be a - finite field, the rationals, or a cyclotomic field. - - Note that the base field characteristic cannot divide the group - order (i.e., the non-modular case). - - ALGORITHM: - - For a finite group `G` in characteristic zero we construct - the Molien series as - - .. MATH:: - - \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\text{det}(I-tg)}, - - where `I` is the identity matrix and `t` an indeterminate. - - For characteristic `p` not dividing the order of `G`, let `k` be - the base field and `N` the order of `G`. Define `\lambda` as a - primitive `N`-th root of unity over `k` and `\omega` as a - primitive `N`-th root of unity over `\QQ`. For each `g \in G` - define `k_i(g)` to be the positive integer such that - `e_i = \lambda^{k_i(g)}` for each eigenvalue `e_i` of `g`. - Then the Molien series is computed as - - .. MATH:: - - \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\prod_{i=1}^n - (1 - t\omega^{k_i(g)})}, - - where `t` is an indeterminant. [Dec1998]_ - - INPUT: - - - ``chi`` -- (default: trivial character) a linear group character of this group - - ``return_series`` -- boolean (default: ``True``) if ``True``, then returns - the Molien series as a power series, ``False`` as a rational function - - ``prec`` -- integer (default: 20); power series default precision - (possibly infinite, in which case it is computed lazily) - - ``variable`` -- string (default: ``'t'``); variable name for the Molien series - - OUTPUT: single variable rational function or power series with integer coefficients - - EXAMPLES:: - - sage: MatrixGroup(matrix(QQ,2,2,[1,1,0,1])).molien_series() - Traceback (most recent call last): - ... - NotImplementedError: only implemented for finite groups - sage: MatrixGroup(matrix(GF(3),2,2,[1,1,0,1])).molien_series() - Traceback (most recent call last): - ... - NotImplementedError: characteristic cannot divide group order - - Tetrahedral Group:: - - sage: K. = CyclotomicField(4) - sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) - sage: Tetra.molien_series(prec=30) - 1 + t^8 + 2*t^12 + t^16 + 2*t^20 + 3*t^24 + 2*t^28 + O(t^30) - sage: mol = Tetra.molien_series(return_series=False); mol - (t^8 - t^4 + 1)/(t^16 - t^12 - t^4 + 1) - sage: mol.parent() - Fraction Field of Univariate Polynomial Ring in t over Integer Ring - sage: chi = Tetra.character(Tetra.character_table()[1]) - sage: Tetra.molien_series(chi, prec=30, variable='u') - u^6 + u^14 + 2*u^18 + u^22 + 2*u^26 + 3*u^30 + 2*u^34 + O(u^36) - sage: chi = Tetra.character(Tetra.character_table()[2]) - sage: Tetra.molien_series(chi) - t^10 + t^14 + t^18 + 2*t^22 + 2*t^26 + O(t^30) - - :: - - sage: S3 = MatrixGroup(SymmetricGroup(3)) - sage: mol = S3.molien_series(prec=10); mol - 1 + t + 2*t^2 + 3*t^3 + 4*t^4 + 5*t^5 + 7*t^6 + 8*t^7 + 10*t^8 + 12*t^9 + O(t^10) - sage: mol.parent() - Power Series Ring in t over Integer Ring - - sage: mol = S3.molien_series(prec=oo); mol - 1 + t + 2*t^2 + 3*t^3 + 4*t^4 + 5*t^5 + 7*t^6 + O(t^7) - sage: mol.parent() - Lazy Taylor Series Ring in t over Integer Ring - - Octahedral Group:: - - sage: K. = CyclotomicField(8) - sage: a = v-v^3 #sqrt(2) - sage: i = v^2 - sage: Octa = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [(1+i)/a,0, 0,(1-i)/a]) - sage: Octa.molien_series(prec=30) - 1 + t^8 + t^12 + t^16 + t^18 + t^20 + 2*t^24 + t^26 + t^28 + O(t^30) - - Icosahedral Group:: - - sage: K. = CyclotomicField(10) - sage: z5 = v^2 - sage: i = z5^5 - sage: a = 2*z5^3 + 2*z5^2 + 1 #sqrt(5) - sage: Ico = MatrixGroup([[z5^3,0, 0,z5^2], [0,1, -1,0], [(z5^4-z5)/a, (z5^2-z5^3)/a, (z5^2-z5^3)/a, -(z5^4-z5)/a]]) - sage: Ico.molien_series(prec=40) - 1 + t^12 + t^20 + t^24 + t^30 + t^32 + t^36 + O(t^40) - - :: - - sage: G = MatrixGroup(CyclicPermutationGroup(3)) - sage: chi = G.character(G.character_table()[1]) - sage: G.molien_series(chi, prec=10) - t + 2*t^2 + 3*t^3 + 5*t^4 + 7*t^5 + 9*t^6 + 12*t^7 + 15*t^8 + 18*t^9 + 22*t^10 + O(t^11) - - :: - - sage: K = GF(5) - sage: S = MatrixGroup(SymmetricGroup(4)) - sage: G = MatrixGroup([matrix(K,4,4,[K(y) for u in m.list() for y in u])for m in S.gens()]) - sage: G.molien_series(return_series=False) - 1/(t^10 - t^9 - t^8 + 2*t^5 - t^2 - t + 1) - - :: - - sage: i = GF(7)(3) - sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) - sage: chi = G.character(G.character_table()[4]) - sage: G.molien_series(chi) - 3*t^5 + 6*t^11 + 9*t^17 + 12*t^23 + O(t^25) - """ - if not self.is_finite(): - raise NotImplementedError("only implemented for finite groups") - if chi is None: - chi = self.trivial_character() - M = self.matrix_space() - R = FractionField(self.base_ring()) - N = self.order() - if R.characteristic() == 0: - P = PolynomialRing(R, variable) - t = P.gen() - # it is possible the character is over a larger cyclotomic field - K = chi.values()[0].parent() - if K.degree() != 1: - if R.degree() != 1: - L = K.composite_fields(R)[0] - else: - L = K - else: - L = R - mol = P(0) - for g in self: - mol += L(chi(g)) / (M.identity_matrix()-t*g.matrix()).det().change_ring(L) - elif R.characteristic().divides(N): - raise NotImplementedError("characteristic cannot divide group order") - else: # char p>0 - # find primitive Nth roots of unity over base ring and QQ - F = cyclotomic_polynomial(N).change_ring(R) - w = F.roots(ring=R.algebraic_closure(), multiplicities=False)[0] - # don't need to extend further in this case since the order of - # the roots of unity in the character divide the order of the group - L = CyclotomicField(N, 'v') - v = L.gen() - # construct Molien series - P = PolynomialRing(L, variable) - t = P.gen() - mol = P(0) - for g in self: - # construct Phi - phi = L(chi(g)) - for e in g.matrix().eigenvalues(): - # find power such that w**n = e - n = 1 - while w**n != e and n < N+1: - n += 1 - # raise v to that power - phi *= (1-t*v**n) - mol += P(1)/phi - # We know the coefficients will be integers - mol = mol.numerator().change_ring(ZZ) / mol.denominator().change_ring(ZZ) - # divide by group order - mol /= N - if return_series: - if prec == float('inf'): - from sage.rings.lazy_series_ring import LazyPowerSeriesRing - PS = LazyPowerSeriesRing(ZZ, names=(variable,), sparse=P.is_sparse()) - else: - PS = PowerSeriesRing(ZZ, variable, default_prec=prec) - return PS(mol) - return mol - - def reynolds_operator(self, poly, chi=None): - r""" - Compute the Reynolds operator of this finite group `G`. - - This is the projection from a polynomial ring to the ring of - relative invariants [Stu1993]_. If possible, the invariant is - returned defined over the base field of the given polynomial - ``poly``, otherwise, it is returned over the compositum of the - fields involved in the computation. - Only implemented for absolute fields. - - ALGORITHM: - - Let `K[x]` be a polynomial ring and `\chi` a linear character for `G`. Let - - .. MATH: - - K[x]^G_{\chi} = \{f \in K[x] | \pi f = \chi(\pi) f \forall \pi\in G\} - - be the ring of invariants of `G` relative to `\chi`. Then the Reynold's operator - is a map `R` from `K[x]` into `K[x]^G_{\chi}` defined by - - .. MATH: - - f \mapsto \frac{1}{|G|} \sum_{ \pi \in G} \chi(\pi) f. - - INPUT: - - - ``poly`` -- a polynomial - - - ``chi`` -- (default: trivial character) a linear group character of this group - - OUTPUT: an invariant polynomial relative to `\chi` - - AUTHORS: - - Rebecca Lauren Miller and Ben Hutz - - EXAMPLES:: - - sage: S3 = MatrixGroup(SymmetricGroup(3)) - sage: R. = QQ[] - sage: f = x*y*z^3 - sage: S3.reynolds_operator(f) - 1/3*x^3*y*z + 1/3*x*y^3*z + 1/3*x*y*z^3 - - :: - - sage: G = MatrixGroup(CyclicPermutationGroup(4)) - sage: chi = G.character(G.character_table()[3]) - sage: K. = CyclotomicField(4) - sage: R. = K[] - sage: G.reynolds_operator(x, chi) - 1/4*x + (1/4*v)*y - 1/4*z + (-1/4*v)*w - sage: chi = G.character(G.character_table()[2]) - sage: R. = QQ[] - sage: G.reynolds_operator(x*y, chi) - 1/4*x*y + (-1/4*zeta4)*y*z + (1/4*zeta4)*x*w - 1/4*z*w - - :: - - sage: K. = CyclotomicField(4) - sage: G = MatrixGroup(CyclicPermutationGroup(3)) - sage: chi = G.character(G.character_table()[1]) - sage: R. = K[] - sage: G.reynolds_operator(x*y^5, chi) - 1/3*x*y^5 + (-2/3*izeta3^3 - izeta3^2 - 8/3*izeta3 - 4/3)*x^5*z + (2/3*izeta3^3 + izeta3^2 + 8/3*izeta3 + 1)*y*z^5 - sage: R. = QQbar[] - sage: G.reynolds_operator(x*y^5, chi) - 1/3*x*y^5 + (-0.1666666666666667? + 0.2886751345948129?*I)*x^5*z + (-0.1666666666666667? - 0.2886751345948129?*I)*y*z^5 - - :: - - sage: K. = CyclotomicField(4) - sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) - sage: chi = Tetra.character(Tetra.character_table()[4]) - sage: L. = QuadraticField(-3) - sage: R. = L[] - sage: Tetra.reynolds_operator(x^4) - 0 - sage: Tetra.reynolds_operator(x^4, chi) - 1/4*x^4 + (1/2*v)*x^2*y^2 + 1/4*y^4 - sage: R.=L[] - sage: LL. = L.extension(x^2+v) - sage: R. = LL[] - sage: Tetra.reynolds_operator(x^4, chi) - Traceback (most recent call last): - ... - NotImplementedError: only implemented for absolute fields - - :: - - sage: G = MatrixGroup(DihedralGroup(4)) - sage: chi = G.character(G.character_table()[1]) - sage: R. = QQ[] - sage: f = x^4 - sage: G.reynolds_operator(f, chi) - Traceback (most recent call last): - ... - TypeError: number of variables in polynomial must match size of matrices - sage: R. = QQ[] - sage: f = x^3*y - sage: G.reynolds_operator(f, chi) - 1/8*x^3*y - 1/8*x*y^3 + 1/8*y^3*z - 1/8*y*z^3 - 1/8*x^3*w + 1/8*z^3*w + - 1/8*x*w^3 - 1/8*z*w^3 - - Characteristic p>0 examples:: - - sage: G = MatrixGroup([[0,1,1,0]]) - sage: R. = GF(2)[] - sage: G.reynolds_operator(x) - Traceback (most recent call last): - ... - NotImplementedError: not implemented when characteristic divides group order - - :: - - sage: i = GF(7)(3) - sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) - sage: chi = G.character(G.character_table()[4]) - sage: R. = GF(7)[] - sage: f = w^5*x + x^6 - sage: G.reynolds_operator(f, chi) - Traceback (most recent call last): - ... - NotImplementedError: nontrivial characters not implemented for characteristic > 0 - sage: G.reynolds_operator(f) - x^6 - - :: - - sage: K = GF(3^2,'t') - sage: G = MatrixGroup([matrix(K,2,2, [0,K.gen(),1,0])]) - sage: R. = GF(3)[] - sage: G.reynolds_operator(x^8) - -x^8 - y^8 - - :: - - sage: K = GF(3^2,'t') - sage: G = MatrixGroup([matrix(GF(3),2,2, [0,1,1,0])]) - sage: R. = K[] - sage: f = -K.gen()*x - sage: G.reynolds_operator(f) - t*x + t*y - """ - if poly.parent().ngens() != self.degree(): - raise TypeError("number of variables in polynomial must match size of matrices") - R = FractionField(poly.base_ring()) - C = FractionField(self.base_ring()) - if chi is None: # then this is the trivial character - if R.characteristic() == 0: - # non-modular case - if C == QQbar or R == QQbar: - L = QQbar - elif not C.is_absolute() or not R.is_absolute(): - raise NotImplementedError("only implemented for absolute fields") - else: # create the compositum - if C.absolute_degree() == 1: - L = R - elif R.absolute_degree() == 1: - L = C - else: - L = C.composite_fields(R)[0] - elif not R.characteristic().divides(self.order()): - if R.characteristic() != C.characteristic(): - raise ValueError("base fields must have same characteristic") - else: - if R.degree() >= C.degree(): - L = R - else: - L = C - else: - raise NotImplementedError("not implemented when characteristic divides group order") - poly = poly.change_ring(L) - poly_gens = vector(poly.parent().gens()) - F = L.zero() - for g in self: - F += poly(*g.matrix()*vector(poly.parent().gens())) - F /= self.order() - return F - # non-trivial character case - K = chi.values()[0].parent() - if R.characteristic() == 0: - # extend base_ring to compositum - if C == QQbar or K == QQbar or R == QQbar: - L = QQbar - elif not C.is_absolute() or not K.is_absolute() or not R.is_absolute(): - raise NotImplementedError("only implemented for absolute fields") - else: - fields = [] - for M in [R,K,C]: - if M.absolute_degree() != 1: - fields.append(M) - l = len(fields) - if l == 0: - # all are QQ - L = R - elif l == 1: - # only one is an extension - L = fields[0] - elif l == 2: - # only two are extensions - L = fields[0].composite_fields(fields[1])[0] - else: - # all three are extensions - L1 = fields[0].composite_fields(fields[1])[0] - L = L1.composite_fields(fields[2])[0] - else: - raise NotImplementedError("nontrivial characters not implemented for characteristic > 0") - poly = poly.change_ring(L) - poly_gens = vector(poly.parent().gens()) - F = L.zero() - for g in self: - F += L(chi(g)) * poly(*g.matrix().change_ring(L)*poly_gens) - F /= self.order() - try: # attempt to move F to base_ring of polynomial - F = F.change_ring(R) - except (TypeError, ValueError): - pass - return F - - def invariants_of_degree(self, deg, chi=None, R=None): - r""" - Return the (relative) invariants of given degree for this group. - - For this group, compute the invariants of degree ``deg`` - with respect to the group character ``chi``. The method - is to project each possible monomial of degree ``deg`` via - the Reynolds operator. Note that if the polynomial ring ``R`` - is specified it's base ring may be extended if the resulting - invariant is defined over a bigger field. - - INPUT: - - - ``degree`` -- a positive integer - - - ``chi`` -- (default: trivial character) a linear group character of this group - - - ``R`` -- (optional) a polynomial ring - - OUTPUT: list of polynomials - - EXAMPLES:: - - sage: Gr = MatrixGroup(SymmetricGroup(2)) - sage: sorted(Gr.invariants_of_degree(3)) - [x0^2*x1 + x0*x1^2, x0^3 + x1^3] - sage: R. = QQ[] - sage: sorted(Gr.invariants_of_degree(4, R=R)) - [x^2*y^2, x^3*y + x*y^3, x^4 + y^4] - - :: - - sage: R. = QQ[] - sage: Gr = MatrixGroup(DihedralGroup(3)) - sage: ct = Gr.character_table() - sage: chi = Gr.character(ct[0]) - sage: all(f(*(g.matrix()*vector(R.gens()))) == chi(g)*f - ....: for f in Gr.invariants_of_degree(3, R=R, chi=chi) for g in Gr) - True - - :: - - sage: i = GF(7)(3) - sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) - sage: G.invariants_of_degree(25) - [] - - :: - - sage: G = MatrixGroup(SymmetricGroup(5)) - sage: R = QQ['x,y'] - sage: G.invariants_of_degree(3, R=R) - Traceback (most recent call last): - ... - TypeError: number of variables in polynomial ring must match size of matrices - - :: - - sage: K. = CyclotomicField(4) - sage: G = MatrixGroup(CyclicPermutationGroup(3)) - sage: chi = G.character(G.character_table()[1]) - sage: R. = K[] - sage: sorted(G.invariants_of_degree(2, R=R, chi=chi)) - [x*y + (-2*izeta3^3 - 3*izeta3^2 - 8*izeta3 - 4)*x*z + (2*izeta3^3 + 3*izeta3^2 + 8*izeta3 + 3)*y*z, - x^2 + (2*izeta3^3 + 3*izeta3^2 + 8*izeta3 + 3)*y^2 + (-2*izeta3^3 - 3*izeta3^2 - 8*izeta3 - 4)*z^2] - - :: - - sage: S3 = MatrixGroup(SymmetricGroup(3)) - sage: chi = S3.character(S3.character_table()[0]) - sage: sorted(S3.invariants_of_degree(5, chi=chi)) - [x0^3*x1^2 - x0^2*x1^3 - x0^3*x2^2 + x1^3*x2^2 + x0^2*x2^3 - x1^2*x2^3, - x0^4*x1 - x0*x1^4 - x0^4*x2 + x1^4*x2 + x0*x2^4 - x1*x2^4] - """ - D = self.degree() - deg = int(deg) - if deg <= 0: - raise ValueError("degree must be a positive integer") - if R is None: - R = PolynomialRing(self.base_ring(), 'x', D) - elif R.ngens() != D: - raise TypeError("number of variables in polynomial ring must match size of matrices") - - ms = self.molien_series(prec=deg+1,chi=chi) - if ms[deg].is_zero(): - return [] - inv = set() - for e in IntegerVectors(deg, D): - F = self.reynolds_operator(R.monomial(*e), chi=chi) - if not F.is_zero(): - F = F / F.lc() - inv.add(F) - if len(inv) == ms[deg]: - break - return list(inv) diff --git a/src/sage/groups/matrix_gps/finitely_generated_gap.py b/src/sage/groups/matrix_gps/finitely_generated_gap.py new file mode 100644 index 00000000000..a58ff05291d --- /dev/null +++ b/src/sage/groups/matrix_gps/finitely_generated_gap.py @@ -0,0 +1,906 @@ +""" +Finitely Generated Matrix Groups with GAP +""" + +# ############################################################################# +# Copyright (C) 2006 William Stein +# 2006-2008 David Joyner +# 2008-2010 Simon King +# 2009 Mike Hansen +# 2011-2013 Volker Braun +# 2013-2023 Travis Scrimshaw +# 2015 Pierre Guillot +# 2017 Ben Hutz +# 2017 Rebecca Lauren Miller +# 2018 Dima Pasechnik +# 2018-2019 Sebastian Oehms +# 2018-2020 Frédéric Chapoton +# 2019 Vincent Delecroix +# 2023 Matthias Koeppe +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# The full text of the GPL is available at: +# +# https://www.gnu.org/licenses/ +# ############################################################################# + +from sage.combinat.integer_vector import IntegerVectors +from sage.groups.matrix_gps.finitely_generated import MatrixGroup +from sage.groups.matrix_gps.matrix_group_gap import MatrixGroup_gap +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.cachefunc import cached_method +from sage.misc.functional import cyclotomic_polynomial +from sage.modules.free_module_element import vector +from sage.rings.fraction_field import FractionField +from sage.rings.integer_ring import ZZ +from sage.rings.number_field.number_field import CyclotomicField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.qqbar import QQbar + + +class FinitelyGeneratedMatrixGroup_gap(MatrixGroup_gap): + """ + Matrix group generated by a finite number of matrices. + + EXAMPLES:: + + sage: m1 = matrix(GF(11), [[1,2],[3,4]]) + sage: m2 = matrix(GF(11), [[1,3],[10,0]]) + sage: G = MatrixGroup(m1, m2); G + Matrix group over Finite Field of size 11 with 2 generators ( + [1 2] [ 1 3] + [3 4], [10 0] + ) + sage: type(G) + + sage: TestSuite(G).run() + """ + + def __reduce__(self): + """ + Implement pickling. + + EXAMPLES:: + + sage: m1 = matrix(QQ, [[1,2],[3,4]]) + sage: m2 = matrix(QQ, [[1,3],[-1,0]]) + sage: loads(MatrixGroup(m1, m2).dumps()) + Matrix group over Rational Field with 2 generators ( + [1 2] [ 1 3] + [3 4], [-1 0] + ) + """ + return (MatrixGroup, + tuple(g.matrix() for g in self.gens()) + ({'check':False},)) + + def as_permutation_group(self, algorithm=None, seed=None): + r""" + Return a permutation group representation for the group. + + In most cases occurring in practice, this is a permutation + group of minimal degree (the degree being determined from + orbits under the group action). When these orbits are hard to + compute, the procedure can be time-consuming and the degree + may not be minimal. + + INPUT: + + - ``algorithm`` -- ``None`` or ``'smaller'``. In the latter + case, try harder to find a permutation representation of + small degree. + - ``seed`` -- ``None`` or an integer specifying the seed + to fix results depending on pseudo-random-numbers. Here + it makes sense to be used with respect to the ``'smaller'`` + option, since GAP produces random output in that context. + + OUTPUT: + + A permutation group isomorphic to ``self``. The + ``algorithm='smaller'`` option tries to return an isomorphic + group of low degree, but is not guaranteed to find the + smallest one and must not even differ from the one obtained + without the option. In that case repeating the invocation + may help (see the example below). + + EXAMPLES:: + + sage: MS = MatrixSpace(GF(2), 5, 5) + sage: A = MS([[0,0,0,0,1],[0,0,0,1,0],[0,0,1,0,0],[0,1,0,0,0],[1,0,0,0,0]]) + sage: G = MatrixGroup([A]) + sage: G.as_permutation_group().order() + 2 + + A finite subgroup of `GL(12,\ZZ)` as a permutation group:: + + sage: imf = libgap.function_factory('ImfMatrixGroup') + sage: GG = imf( 12, 3 ) + sage: G = MatrixGroup(GG.GeneratorsOfGroup()) + sage: G.cardinality() + 21499084800 + sage: P = G.as_permutation_group() + sage: Psmaller = G.as_permutation_group(algorithm="smaller", seed=6) + sage: P == Psmaller + False + sage: P.cardinality() + 21499084800 + sage: P.degree() + 144 + sage: Psmaller.cardinality() + 21499084800 + sage: Psmaller.degree() # random + 80 + + .. NOTE:: + + In this case, the "smaller" option returned an isomorphic + group of lower degree. The above example used GAP's library + of irreducible maximal finite ("imf") integer matrix groups + to construct the :class:`MatrixGroup` `G` over `\GF{7}`. The section + "Irreducible Maximal Finite Integral Matrix Groups" in the + GAP reference manual has more details. + + .. NOTE:: + + Concerning the option ``algorithm='smaller'`` you should note + the following from GAP documentation: "The methods used might + involve the use of random elements and the permutation + representation (or even the degree of the representation) is + not guaranteed to be the same for different calls of + ``SmallerDegreePermutationRepresentation``." + + To obtain a reproducible result the optional argument ``seed`` + may be used as in the example above. + + TESTS:: + + sage: A = matrix(QQ, 2, [0, 1, 1, 0]) + sage: B = matrix(QQ, 2, [1, 0, 0, 1]) + sage: a, b = MatrixGroup([A, B]).as_permutation_group().gens() + sage: a.order(), b.order() + (2, 1) + + The above example in `GL(12,\ZZ)`, reduced modulo 7:: + + sage: MS = MatrixSpace(GF(7), 12, 12) + sage: G = MatrixGroup([MS(g) for g in GG.GeneratorsOfGroup()]) + sage: G.cardinality() + 21499084800 + sage: P = G.as_permutation_group() + sage: P.cardinality() + 21499084800 + + Check that large degree is still working:: + + sage: Sp(6,3).as_permutation_group().cardinality() + 9170703360 + + Check that :trac:`25706` still works after :trac:`26903`:: + + sage: MG = GU(3,2).as_matrix_group() + sage: PG = MG.as_permutation_group() + sage: mg = MG.an_element() + sage: PG(mg).order() # particular element depends on the set of GAP packages installed + 6 + """ + # Note that the output of IsomorphismPermGroup() depends on + # memory locations and will change if you change the order of + # doctests and/or architecture + from sage.groups.perm_gps.permgroup import PermutationGroup + if not self.is_finite(): + raise NotImplementedError("group must be finite") + if seed is not None: + from sage.libs.gap.libgap import libgap + libgap.set_seed(ZZ(seed)) + iso = self._libgap_().IsomorphismPermGroup() + if algorithm == "smaller": + iso = iso.Image().SmallerDegreePermutationRepresentation() + return PermutationGroup(iso.Image().GeneratorsOfGroup().sage(), + canonicalize=False) + + def module_composition_factors(self, algorithm=None): + r""" + Return a list of triples consisting of [base field, dimension, + irreducibility], for each of the Meataxe composition factors + modules. The ``algorithm="verbose"`` option returns more information, + but in Meataxe notation. + + EXAMPLES:: + + sage: F = GF(3); MS = MatrixSpace(F,4,4) + sage: M = MS(0) + sage: M[0,1]=1;M[1,2]=1;M[2,3]=1;M[3,0]=1 + sage: G = MatrixGroup([M]) + sage: G.module_composition_factors() + [(Finite Field of size 3, 1, True), + (Finite Field of size 3, 1, True), + (Finite Field of size 3, 2, True)] + sage: F = GF(7); MS = MatrixSpace(F,2,2) + sage: gens = [MS([[0,1],[-1,0]]),MS([[1,1],[2,3]])] + sage: G = MatrixGroup(gens) + sage: G.module_composition_factors() + [(Finite Field of size 7, 2, True)] + + Type ``G.module_composition_factors(algorithm='verbose')`` to get a + more verbose version. + + For more on MeatAxe notation, see + https://www.gap-system.org/Manuals/doc/ref/chap69.html + """ + from sage.libs.gap.libgap import libgap + F = self.base_ring() + if not F.is_finite(): + raise NotImplementedError("base ring must be finite") + n = self.degree() + MS = MatrixSpace(F, n, n) + mats = [MS(g.matrix()) for g in self.gens()] + # initializing list of mats by which the gens act on self + mats_gap = libgap(mats) + M = mats_gap.GModuleByMats(F) + compo = libgap.function_factory('MTX.CompositionFactors') + MCFs = compo(M) + if algorithm == "verbose": + print(str(MCFs) + "\n") + return sorted((MCF['field'].sage(), + MCF['dimension'].sage(), + MCF['IsIrreducible'].sage()) for MCF in MCFs) + + def invariant_generators(self): + r""" + Return invariant ring generators. + + Computes generators for the polynomial ring + `F[x_1,\ldots,x_n]^G`, where `G` in `GL(n,F)` is a finite matrix + group. + + In the "good characteristic" case the polynomials returned + form a minimal generating set for the algebra of `G`-invariant + polynomials. In the "bad" case, the polynomials returned + are primary and secondary invariants, forming a not + necessarily minimal generating set for the algebra of + `G`-invariant polynomials. + + ALGORITHM: + + Wraps Singular's ``invariant_algebra_reynolds`` and ``invariant_ring`` + in ``finvar.lib``. + + EXAMPLES:: + + sage: F = GF(7); MS = MatrixSpace(F,2,2) + sage: gens = [MS([[0,1],[-1,0]]),MS([[1,1],[2,3]])] + sage: G = MatrixGroup(gens) + sage: G.invariant_generators() + [x1^7*x2 - x1*x2^7, + x1^12 - 2*x1^9*x2^3 - x1^6*x2^6 + 2*x1^3*x2^9 + x2^12, + x1^18 + 2*x1^15*x2^3 + 3*x1^12*x2^6 + 3*x1^6*x2^12 - 2*x1^3*x2^15 + x2^18] + + sage: q = 4; a = 2 + sage: MS = MatrixSpace(QQ, 2, 2) + sage: gen1 = [[1/a,(q-1)/a],[1/a, -1/a]]; gen2 = [[1,0],[0,-1]]; gen3 = [[-1,0],[0,1]] + sage: G = MatrixGroup([MS(gen1),MS(gen2),MS(gen3)]) + sage: G.cardinality() + 12 + sage: G.invariant_generators() + [x1^2 + 3*x2^2, x1^6 + 15*x1^4*x2^2 + 15*x1^2*x2^4 + 33*x2^6] + + sage: F = CyclotomicField(8) + sage: z = F.gen() + sage: a = z+1/z + sage: b = z^2 + sage: MS = MatrixSpace(F,2,2) + sage: g1 = MS([[1/a, 1/a], [1/a, -1/a]]) + sage: g2 = MS([[-b, 0], [0, b]]) + sage: G = MatrixGroup([g1,g2]) + sage: G.invariant_generators() + [x1^4 + 2*x1^2*x2^2 + x2^4, + x1^5*x2 - x1*x2^5, + x1^8 + 28/9*x1^6*x2^2 + 70/9*x1^4*x2^4 + 28/9*x1^2*x2^6 + x2^8] + + AUTHORS: + + - David Joyner, Simon King and Martin Albrecht. + + REFERENCES: + + - Singular reference manual + + - [Stu1993]_ + + - S. King, "Minimal Generating Sets of non-modular invariant + rings of finite groups", :arxiv:`math/0703035`. + """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + from sage.interfaces.singular import singular + gens = self.gens() + singular.LIB("finvar.lib") + n = self.degree() # len((gens[0].matrix()).rows()) + F = self.base_ring() + q = F.characteristic() + # test if the field is admissible + if F.gen() == 1: # we got the rationals or GF(prime) + FieldStr = str(F.characteristic()) + elif hasattr(F,'polynomial'): # we got an algebraic extension + if len(F.gens()) > 1: + raise NotImplementedError("can only deal with finite fields and (simple algebraic extensions of) the rationals") + FieldStr = '(%d,%s)' % (F.characteristic(), str(F.gen())) + else: # we have a transcendental extension + FieldStr = '(%d,%s)' % (F.characteristic(), + ','.join(str(p) for p in F.gens())) + + # Setting Singular's variable names + # We need to make sure that field generator and variables get different names. + if str(F.gen())[0] == 'x': + VarStr = 'y' + else: + VarStr = 'x' + VarNames = '(' + ','.join((VarStr+str(i) for i in range(1, n+1)))+')' + # The function call and affectation below have side-effects. Do not remove! + # (even if pyflakes say so) + R = singular.ring(FieldStr, VarNames, 'dp') + if hasattr(F, 'polynomial') and F.gen() != 1: + # we have to define minpoly + singular.eval('minpoly = '+str(F.polynomial()).replace('x',str(F.gen()))) + A = [singular.matrix(n,n,str((x.matrix()).list())) for x in gens] + Lgens = ','.join((x.name() for x in A)) + PR = PolynomialRing(F, n, [VarStr+str(i) for i in range(1,n+1)]) + + if q == 0 or (q > 0 and self.cardinality() % q): + from sage.matrix.constructor import Matrix + try: + elements = [g.matrix() for g in self.list()] + except (TypeError, ValueError): + elements + if elements is not None: + ReyName = 't'+singular._next_var_name() + singular.eval('matrix %s[%d][%d]' % (ReyName, + self.cardinality(), n)) + for i in range(1,self.cardinality()+1): + M = Matrix(F, elements[i-1]) + D = [{} for foobar in range(self.degree())] + for x,y in M.dict().items(): + D[x[0]][x[1]] = y + for row in range(self.degree()): + for t in D[row].items(): + singular.eval('%s[%d,%d]=%s[%d,%d]+(%s)*var(%d)' + % (ReyName,i,row+1,ReyName,i,row+1, repr(t[1]),t[0]+1)) + IRName = 't'+singular._next_var_name() + singular.eval('matrix %s = invariant_algebra_reynolds(%s)' % (IRName,ReyName)) + else: + ReyName = 't'+singular._next_var_name() + singular.eval('list %s=group_reynolds((%s))' % (ReyName, Lgens)) + IRName = 't'+singular._next_var_name() + singular.eval('matrix %s = invariant_algebra_reynolds(%s[1])' % (IRName, ReyName)) + + OUT = [singular.eval(IRName+'[1,%d]' % (j)) + for j in range(1, 1+int(singular('ncols('+IRName+')')))] + return [PR(gen) for gen in OUT] + if self.cardinality() % q == 0: + PName = 't' + singular._next_var_name() + SName = 't' + singular._next_var_name() + singular.eval('matrix %s,%s=invariant_ring(%s)' % (PName, SName, Lgens)) + OUT = [singular.eval(PName+'[1,%d]' % (j)) + for j in range(1,1+singular('ncols('+PName+')'))] + OUT += [singular.eval(SName+'[1,%d]' % (j)) + for j in range(2,1+singular('ncols('+SName+')'))] + return [PR(gen) for gen in OUT] + + def molien_series(self, chi=None, return_series=True, prec=20, variable='t'): + r""" + Compute the Molien series of this finite group with respect to the + character ``chi``. + + It can be returned either as a rational function in one variable + or a power series in one variable. The base field must be a + finite field, the rationals, or a cyclotomic field. + + Note that the base field characteristic cannot divide the group + order (i.e., the non-modular case). + + ALGORITHM: + + For a finite group `G` in characteristic zero we construct + the Molien series as + + .. MATH:: + + \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\text{det}(I-tg)}, + + where `I` is the identity matrix and `t` an indeterminate. + + For characteristic `p` not dividing the order of `G`, let `k` be + the base field and `N` the order of `G`. Define `\lambda` as a + primitive `N`-th root of unity over `k` and `\omega` as a + primitive `N`-th root of unity over `\QQ`. For each `g \in G` + define `k_i(g)` to be the positive integer such that + `e_i = \lambda^{k_i(g)}` for each eigenvalue `e_i` of `g`. + Then the Molien series is computed as + + .. MATH:: + + \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\prod_{i=1}^n + (1 - t\omega^{k_i(g)})}, + + where `t` is an indeterminant. [Dec1998]_ + + INPUT: + + - ``chi`` -- (default: trivial character) a linear group character of this group + - ``return_series`` -- boolean (default: ``True``) if ``True``, then returns + the Molien series as a power series, ``False`` as a rational function + - ``prec`` -- integer (default: 20); power series default precision + (possibly infinite, in which case it is computed lazily) + - ``variable`` -- string (default: ``'t'``); variable name for the Molien series + + OUTPUT: single variable rational function or power series with integer coefficients + + EXAMPLES:: + + sage: MatrixGroup(matrix(QQ,2,2,[1,1,0,1])).molien_series() + Traceback (most recent call last): + ... + NotImplementedError: only implemented for finite groups + sage: MatrixGroup(matrix(GF(3),2,2,[1,1,0,1])).molien_series() + Traceback (most recent call last): + ... + NotImplementedError: characteristic cannot divide group order + + Tetrahedral Group:: + + sage: K. = CyclotomicField(4) + sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) + sage: Tetra.molien_series(prec=30) + 1 + t^8 + 2*t^12 + t^16 + 2*t^20 + 3*t^24 + 2*t^28 + O(t^30) + sage: mol = Tetra.molien_series(return_series=False); mol + (t^8 - t^4 + 1)/(t^16 - t^12 - t^4 + 1) + sage: mol.parent() + Fraction Field of Univariate Polynomial Ring in t over Integer Ring + sage: chi = Tetra.character(Tetra.character_table()[1]) + sage: Tetra.molien_series(chi, prec=30, variable='u') + u^6 + u^14 + 2*u^18 + u^22 + 2*u^26 + 3*u^30 + 2*u^34 + O(u^36) + sage: chi = Tetra.character(Tetra.character_table()[2]) + sage: Tetra.molien_series(chi) + t^10 + t^14 + t^18 + 2*t^22 + 2*t^26 + O(t^30) + + :: + + sage: S3 = MatrixGroup(SymmetricGroup(3)) + sage: mol = S3.molien_series(prec=10); mol + 1 + t + 2*t^2 + 3*t^3 + 4*t^4 + 5*t^5 + 7*t^6 + 8*t^7 + 10*t^8 + 12*t^9 + O(t^10) + sage: mol.parent() + Power Series Ring in t over Integer Ring + + sage: mol = S3.molien_series(prec=oo); mol + 1 + t + 2*t^2 + 3*t^3 + 4*t^4 + 5*t^5 + 7*t^6 + O(t^7) + sage: mol.parent() + Lazy Taylor Series Ring in t over Integer Ring + + Octahedral Group:: + + sage: K. = CyclotomicField(8) + sage: a = v-v^3 #sqrt(2) + sage: i = v^2 + sage: Octa = MatrixGroup([(-1+i)/2, (-1+i)/2, (1+i)/2, (-1-i)/2], + ....: [(1+i)/a, 0, 0, (1-i)/a]) + sage: Octa.molien_series(prec=30) + 1 + t^8 + t^12 + t^16 + t^18 + t^20 + 2*t^24 + t^26 + t^28 + O(t^30) + + Icosahedral Group:: + + sage: K. = CyclotomicField(10) + sage: z5 = v^2 + sage: i = z5^5 + sage: a = 2*z5^3 + 2*z5^2 + 1 #sqrt(5) + sage: Ico = MatrixGroup([[z5^3,0, 0,z5^2], + ....: [0,1, -1,0], + ....: [(z5^4-z5)/a, (z5^2-z5^3)/a, (z5^2-z5^3)/a, -(z5^4-z5)/a]]) + sage: Ico.molien_series(prec=40) + 1 + t^12 + t^20 + t^24 + t^30 + t^32 + t^36 + O(t^40) + + :: + + sage: G = MatrixGroup(CyclicPermutationGroup(3)) + sage: chi = G.character(G.character_table()[1]) + sage: G.molien_series(chi, prec=10) + t + 2*t^2 + 3*t^3 + 5*t^4 + 7*t^5 + 9*t^6 + + 12*t^7 + 15*t^8 + 18*t^9 + 22*t^10 + O(t^11) + + :: + + sage: K = GF(5) + sage: S = MatrixGroup(SymmetricGroup(4)) + sage: G = MatrixGroup([matrix(K, 4, 4, [K(y) for u in m.list() for y in u]) + ....: for m in S.gens()]) + sage: G.molien_series(return_series=False) + 1/(t^10 - t^9 - t^8 + 2*t^5 - t^2 - t + 1) + + :: + + sage: i = GF(7)(3) + sage: G = MatrixGroup([[i^3,0, 0,-i^3], [i^2,0, 0,-i^2]]) + sage: chi = G.character(G.character_table()[4]) + sage: G.molien_series(chi) + 3*t^5 + 6*t^11 + 9*t^17 + 12*t^23 + O(t^25) + """ + if not self.is_finite(): + raise NotImplementedError("only implemented for finite groups") + if chi is None: + chi = self.trivial_character() + M = self.matrix_space() + R = FractionField(self.base_ring()) + N = self.order() + if R.characteristic() == 0: + P = PolynomialRing(R, variable) + t = P.gen() + # it is possible the character is over a larger cyclotomic field + K = chi.values()[0].parent() + if K.degree() != 1: + if R.degree() != 1: + L = K.composite_fields(R)[0] + else: + L = K + else: + L = R + mol = P(0) + for g in self: + mol += L(chi(g)) / (M.identity_matrix()-t*g.matrix()).det().change_ring(L) + elif R.characteristic().divides(N): + raise NotImplementedError("characteristic cannot divide group order") + else: # char p>0 + # find primitive Nth roots of unity over base ring and QQ + F = cyclotomic_polynomial(N).change_ring(R) + w = F.roots(ring=R.algebraic_closure(), multiplicities=False)[0] + # don't need to extend further in this case since the order of + # the roots of unity in the character divide the order of the group + L = CyclotomicField(N, 'v') + v = L.gen() + # construct Molien series + P = PolynomialRing(L, variable) + t = P.gen() + mol = P(0) + for g in self: + # construct Phi + phi = L(chi(g)) + for e in g.matrix().eigenvalues(): + # find power such that w**n = e + n = 1 + while w**n != e and n < N+1: + n += 1 + # raise v to that power + phi *= (1-t*v**n) + mol += P(1)/phi + # We know the coefficients will be integers + mol = mol.numerator().change_ring(ZZ) / mol.denominator().change_ring(ZZ) + # divide by group order + mol /= N + if return_series: + if prec == float('inf'): + from sage.rings.lazy_series_ring import LazyPowerSeriesRing + PS = LazyPowerSeriesRing(ZZ, names=(variable,), sparse=P.is_sparse()) + else: + PS = PowerSeriesRing(ZZ, variable, default_prec=prec) + return PS(mol) + return mol + + def reynolds_operator(self, poly, chi=None): + r""" + Compute the Reynolds operator of this finite group `G`. + + This is the projection from a polynomial ring to the ring of + relative invariants [Stu1993]_. If possible, the invariant is + returned defined over the base field of the given polynomial + ``poly``, otherwise, it is returned over the compositum of the + fields involved in the computation. + Only implemented for absolute fields. + + ALGORITHM: + + Let `K[x]` be a polynomial ring and `\chi` a linear character for `G`. Let + + .. MATH: + + K[x]^G_{\chi} = \{f \in K[x] | \pi f = \chi(\pi) f \forall \pi\in G\} + + be the ring of invariants of `G` relative to `\chi`. Then the Reynolds operator + is a map `R` from `K[x]` into `K[x]^G_{\chi}` defined by + + .. MATH: + + f \mapsto \frac{1}{|G|} \sum_{ \pi \in G} \chi(\pi) f. + + INPUT: + + - ``poly`` -- a polynomial + + - ``chi`` -- (default: trivial character) a linear group character of this group + + OUTPUT: an invariant polynomial relative to `\chi` + + AUTHORS: + + Rebecca Lauren Miller and Ben Hutz + + EXAMPLES:: + + sage: S3 = MatrixGroup(SymmetricGroup(3)) + sage: R. = QQ[] + sage: f = x*y*z^3 + sage: S3.reynolds_operator(f) + 1/3*x^3*y*z + 1/3*x*y^3*z + 1/3*x*y*z^3 + + :: + + sage: G = MatrixGroup(CyclicPermutationGroup(4)) + sage: chi = G.character(G.character_table()[3]) + sage: K. = CyclotomicField(4) + sage: R. = K[] + sage: G.reynolds_operator(x, chi) + 1/4*x + (1/4*v)*y - 1/4*z + (-1/4*v)*w + sage: chi = G.character(G.character_table()[2]) + sage: R. = QQ[] + sage: G.reynolds_operator(x*y, chi) + 1/4*x*y + (-1/4*zeta4)*y*z + (1/4*zeta4)*x*w - 1/4*z*w + + :: + + sage: K. = CyclotomicField(4) + sage: G = MatrixGroup(CyclicPermutationGroup(3)) + sage: chi = G.character(G.character_table()[1]) + sage: R. = K[] + sage: G.reynolds_operator(x*y^5, chi) + 1/3*x*y^5 + (-2/3*izeta3^3 - izeta3^2 - 8/3*izeta3 - 4/3)*x^5*z + + (2/3*izeta3^3 + izeta3^2 + 8/3*izeta3 + 1)*y*z^5 + sage: R. = QQbar[] + sage: G.reynolds_operator(x*y^5, chi) + 1/3*x*y^5 + (-0.1666666666666667? + 0.2886751345948129?*I)*x^5*z + + (-0.1666666666666667? - 0.2886751345948129?*I)*y*z^5 + + :: + + sage: K. = CyclotomicField(4) + sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) + sage: chi = Tetra.character(Tetra.character_table()[4]) + sage: L. = QuadraticField(-3) + sage: R. = L[] + sage: Tetra.reynolds_operator(x^4) + 0 + sage: Tetra.reynolds_operator(x^4, chi) + 1/4*x^4 + (1/2*v)*x^2*y^2 + 1/4*y^4 + sage: R.=L[] + sage: LL. = L.extension(x^2 + v) + sage: R. = LL[] + sage: Tetra.reynolds_operator(x^4, chi) + Traceback (most recent call last): + ... + NotImplementedError: only implemented for absolute fields + + :: + + sage: G = MatrixGroup(DihedralGroup(4)) + sage: chi = G.character(G.character_table()[1]) + sage: R. = QQ[] + sage: f = x^4 + sage: G.reynolds_operator(f, chi) + Traceback (most recent call last): + ... + TypeError: number of variables in polynomial must match size of matrices + sage: R. = QQ[] + sage: f = x^3*y + sage: G.reynolds_operator(f, chi) + 1/8*x^3*y - 1/8*x*y^3 + 1/8*y^3*z - 1/8*y*z^3 - 1/8*x^3*w + 1/8*z^3*w + + 1/8*x*w^3 - 1/8*z*w^3 + + Characteristic `p>0` examples:: + + sage: G = MatrixGroup([[0,1, 1,0]]) + sage: R. = GF(2)[] + sage: G.reynolds_operator(x) + Traceback (most recent call last): + ... + NotImplementedError: not implemented when characteristic divides group order + + :: + + sage: i = GF(7)(3) + sage: G = MatrixGroup([[i^3,0, 0,-i^3], [i^2,0, 0,-i^2]]) + sage: chi = G.character(G.character_table()[4]) + sage: R. = GF(7)[] + sage: f = w^5*x + x^6 + sage: G.reynolds_operator(f, chi) + Traceback (most recent call last): + ... + NotImplementedError: nontrivial characters not implemented for characteristic > 0 + sage: G.reynolds_operator(f) + x^6 + + :: + + sage: K = GF(3^2,'t') + sage: G = MatrixGroup([matrix(K, 2, 2, [0,K.gen(), 1,0])]) + sage: R. = GF(3)[] + sage: G.reynolds_operator(x^8) + -x^8 - y^8 + + :: + + sage: K = GF(3^2,'t') + sage: G = MatrixGroup([matrix(GF(3), 2, 2, [0,1, 1,0])]) + sage: R. = K[] + sage: f = -K.gen()*x + sage: G.reynolds_operator(f) + t*x + t*y + """ + if poly.parent().ngens() != self.degree(): + raise TypeError("number of variables in polynomial must match size of matrices") + R = FractionField(poly.base_ring()) + C = FractionField(self.base_ring()) + if chi is None: # then this is the trivial character + if R.characteristic() == 0: + # non-modular case + if C == QQbar or R == QQbar: + L = QQbar + elif not C.is_absolute() or not R.is_absolute(): + raise NotImplementedError("only implemented for absolute fields") + else: # create the compositum + if C.absolute_degree() == 1: + L = R + elif R.absolute_degree() == 1: + L = C + else: + L = C.composite_fields(R)[0] + elif not R.characteristic().divides(self.order()): + if R.characteristic() != C.characteristic(): + raise ValueError("base fields must have same characteristic") + else: + if R.degree() >= C.degree(): + L = R + else: + L = C + else: + raise NotImplementedError("not implemented when characteristic divides group order") + poly = poly.change_ring(L) + poly_gens = vector(poly.parent().gens()) + F = L.zero() + for g in self: + F += poly(*g.matrix()*vector(poly.parent().gens())) + F /= self.order() + return F + # non-trivial character case + K = chi.values()[0].parent() + if R.characteristic() == 0: + # extend base_ring to compositum + if C == QQbar or K == QQbar or R == QQbar: + L = QQbar + elif not C.is_absolute() or not K.is_absolute() or not R.is_absolute(): + raise NotImplementedError("only implemented for absolute fields") + else: + fields = [] + for M in [R,K,C]: + if M.absolute_degree() != 1: + fields.append(M) + l = len(fields) + if l == 0: + # all are QQ + L = R + elif l == 1: + # only one is an extension + L = fields[0] + elif l == 2: + # only two are extensions + L = fields[0].composite_fields(fields[1])[0] + else: + # all three are extensions + L1 = fields[0].composite_fields(fields[1])[0] + L = L1.composite_fields(fields[2])[0] + else: + raise NotImplementedError("nontrivial characters not implemented for characteristic > 0") + poly = poly.change_ring(L) + poly_gens = vector(poly.parent().gens()) + F = L.zero() + for g in self: + F += L(chi(g)) * poly(*g.matrix().change_ring(L)*poly_gens) + F /= self.order() + try: # attempt to move F to base_ring of polynomial + F = F.change_ring(R) + except (TypeError, ValueError): + pass + return F + + def invariants_of_degree(self, deg, chi=None, R=None): + r""" + Return the (relative) invariants of given degree for this group. + + For this group, compute the invariants of degree ``deg`` + with respect to the group character ``chi``. The method + is to project each possible monomial of degree ``deg`` via + the Reynolds operator. Note that if the polynomial ring ``R`` + is specified it's base ring may be extended if the resulting + invariant is defined over a bigger field. + + INPUT: + + - ``degree`` -- a positive integer + + - ``chi`` -- (default: trivial character) a linear group character of this group + + - ``R`` -- (optional) a polynomial ring + + OUTPUT: list of polynomials + + EXAMPLES:: + + sage: Gr = MatrixGroup(SymmetricGroup(2)) + sage: sorted(Gr.invariants_of_degree(3)) + [x0^2*x1 + x0*x1^2, x0^3 + x1^3] + sage: R. = QQ[] + sage: sorted(Gr.invariants_of_degree(4, R=R)) + [x^2*y^2, x^3*y + x*y^3, x^4 + y^4] + + :: + + sage: R. = QQ[] + sage: Gr = MatrixGroup(DihedralGroup(3)) + sage: ct = Gr.character_table() + sage: chi = Gr.character(ct[0]) + sage: all(f(*(g.matrix()*vector(R.gens()))) == chi(g)*f + ....: for f in Gr.invariants_of_degree(3, R=R, chi=chi) for g in Gr) + True + + :: + + sage: i = GF(7)(3) + sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) + sage: G.invariants_of_degree(25) + [] + + :: + + sage: G = MatrixGroup(SymmetricGroup(5)) + sage: R = QQ['x,y'] + sage: G.invariants_of_degree(3, R=R) + Traceback (most recent call last): + ... + TypeError: number of variables in polynomial ring must match size of matrices + + :: + + sage: K. = CyclotomicField(4) + sage: G = MatrixGroup(CyclicPermutationGroup(3)) + sage: chi = G.character(G.character_table()[1]) + sage: R. = K[] + sage: sorted(G.invariants_of_degree(2, R=R, chi=chi)) + [x*y + (-2*izeta3^3 - 3*izeta3^2 - 8*izeta3 - 4)*x*z + + (2*izeta3^3 + 3*izeta3^2 + 8*izeta3 + 3)*y*z, + x^2 + (2*izeta3^3 + 3*izeta3^2 + 8*izeta3 + 3)*y^2 + + (-2*izeta3^3 - 3*izeta3^2 - 8*izeta3 - 4)*z^2] + + :: + + sage: S3 = MatrixGroup(SymmetricGroup(3)) + sage: chi = S3.character(S3.character_table()[0]) + sage: sorted(S3.invariants_of_degree(5, chi=chi)) + [x0^3*x1^2 - x0^2*x1^3 - x0^3*x2^2 + x1^3*x2^2 + x0^2*x2^3 - x1^2*x2^3, + x0^4*x1 - x0*x1^4 - x0^4*x2 + x1^4*x2 + x0*x2^4 - x1*x2^4] + """ + D = self.degree() + deg = int(deg) + if deg <= 0: + raise ValueError("degree must be a positive integer") + if R is None: + R = PolynomialRing(self.base_ring(), 'x', D) + elif R.ngens() != D: + raise TypeError("number of variables in polynomial ring must match size of matrices") + + ms = self.molien_series(prec=deg+1,chi=chi) + if ms[deg].is_zero(): + return [] + inv = set() + for e in IntegerVectors(deg, D): + F = self.reynolds_operator(R.monomial(*e), chi=chi) + if not F.is_zero(): + F = F / F.lc() + inv.add(F) + if len(inv) == ms[deg]: + break + return list(inv) diff --git a/src/sage/groups/matrix_gps/group_element.pxd b/src/sage/groups/matrix_gps/group_element.pxd index df5f99aeca9..36a5a9fc4ce 100644 --- a/src/sage/groups/matrix_gps/group_element.pxd +++ b/src/sage/groups/matrix_gps/group_element.pxd @@ -1,5 +1,4 @@ from sage.structure.element cimport MultiplicativeGroupElement, Element, MonoidElement, Matrix -from sage.groups.libgap_wrapper cimport ElementLibGAP cpdef is_MatrixGroupElement(x) @@ -9,8 +8,3 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): cpdef _act_on_(self, x, bint self_on_left) cpdef _mul_(self, other) cpdef list list(self) - -cdef class MatrixGroupElement_gap(ElementLibGAP): - cpdef _act_on_(self, x, bint self_on_left) - cpdef list list(self) - diff --git a/src/sage/groups/matrix_gps/group_element.pyx b/src/sage/groups/matrix_gps/group_element.pyx index 4d0312efdf9..c18476a45ba 100644 --- a/src/sage/groups/matrix_gps/group_element.pyx +++ b/src/sage/groups/matrix_gps/group_element.pyx @@ -3,16 +3,15 @@ Matrix Group Elements EXAMPLES:: - sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] - sage: G = MatrixGroup(gens); G + sage: F = GF(3); MS = MatrixSpace(F, 2, 2) # optional - sage.rings.finite_rings + sage: gens = [MS([[1,0], [0,1]]), MS([[1,1], [0,1]])] # optional - sage.rings.finite_rings + sage: G = MatrixGroup(gens); G # optional - sage.rings.finite_rings Matrix group over Finite Field of size 3 with 2 generators ( [1 0] [1 1] - [0 1], [0 1] - ) - sage: g = G([[1,1],[0,1]]) - sage: h = G([[1,2],[0,1]]) - sage: g*h + [0 1], [0 1] ) + sage: g = G([[1,1], [0,1]]) # optional - sage.rings.finite_rings + sage: h = G([[1,2], [0,1]]) # optional - sage.rings.finite_rings + sage: g*h # optional - sage.rings.finite_rings [1 0] [0 1] @@ -20,7 +19,7 @@ You cannot add two matrices, since this is not a group operation. You can coerce matrices back to the matrix space and add them there:: - sage: g + h + sage: g + h # optional - sage.rings.finite_rings Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for +: @@ -33,20 +32,20 @@ there:: [0 1], [0 1] )' - sage: g.matrix() + h.matrix() + sage: g.matrix() + h.matrix() # optional - sage.rings.finite_rings [2 0] [0 2] Similarly, you cannot multiply group elements by scalars but you can do it with the underlying matrices:: - sage: 2*g + sage: 2*g # optional - sage.rings.finite_rings Traceback (most recent call last): ... - TypeError: unsupported operand parent(s) for *: 'Integer Ring' and 'Matrix group over Finite Field of size 3 with 2 generators ( + TypeError: unsupported operand parent(s) for *: 'Integer Ring' + and 'Matrix group over Finite Field of size 3 with 2 generators ( [1 0] [1 1] - [0 1], [0 1] - )' + [0 1], [0 1] )' AUTHORS: @@ -64,9 +63,11 @@ AUTHORS: """ #***************************************************************************** -# Copyright (C) 2006 David Joyner and William Stein -# Copyright (C) 2013 Volker Braun -# Copyright (C) 2016 Travis Scrimshaw +# Copyright (C) 2006 David Joyner and William Stein +# 2013 Volker Braun +# 2016 Travis Scrimshaw +# 2016-2018 Jeroen Demeyer +# 2023 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -75,16 +76,18 @@ AUTHORS: # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.structure.element cimport MultiplicativeGroupElement, Element, MonoidElement, Matrix +from sage.misc.cachefunc import cached_method +from sage.rings.integer_ring import ZZ +from sage.structure.element cimport MultiplicativeGroupElement, Matrix +from sage.structure.element import is_Matrix from sage.structure.parent cimport Parent from sage.structure.richcmp cimport richcmp -from sage.libs.gap.element cimport GapElement, GapElement_List -from sage.groups.libgap_wrapper cimport ElementLibGAP -from sage.structure.element import is_Matrix -from sage.structure.factorization import Factorization -from sage.misc.cachefunc import cached_method -from sage.rings.integer_ring import ZZ + +try: + from .group_element_gap import MatrixGroupElement_gap +except ImportError: + MatrixGroupElement_gap = () cpdef is_MatrixGroupElement(x): @@ -95,9 +98,7 @@ cpdef is_MatrixGroupElement(x): - ``x`` -- anything. - OUTPUT: - - Boolean. + OUTPUT: Boolean. EXAMPLES:: @@ -105,8 +106,8 @@ cpdef is_MatrixGroupElement(x): sage: is_MatrixGroupElement('helloooo') False - sage: G = GL(2,3) - sage: is_MatrixGroupElement(G.an_element()) + sage: G = GL(2,3) # optional - sage.rings.finite_rings + sage: is_MatrixGroupElement(G.an_element()) # optional - sage.rings.finite_rings True """ return isinstance(x, (MatrixGroupElement_generic, MatrixGroupElement_gap)) @@ -130,16 +131,15 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): - ``parent`` -- the parent - ``check`` -- bool (default: ``True``); if ``True``, then - does some type checking + do some type checking - ``convert`` -- bool (default: ``True``); if ``True``, then convert ``M`` to the right matrix space EXAMPLES:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.an_element() - sage: g + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.an_element(); g # optional - sage.combinat [ 0 0 -1] [ 1 0 -1] [ 0 1 -1] @@ -150,9 +150,9 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): TESTS:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.an_element() - sage: TestSuite(g).run() + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.an_element() # optional - sage.combinat + sage: TestSuite(g).run() # optional - sage.combinat """ if convert: M = parent.matrix_space()(M) @@ -173,9 +173,9 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): r""" TESTS:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.an_element() - sage: hash(g) + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.an_element() # optional - sage.combinat + sage: hash(g) # optional - sage.combinat 660522311176098153 # 64-bit -606138007 # 32-bit """ @@ -187,9 +187,9 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): TESTS:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.an_element() - sage: loads(g.dumps()) == g + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.an_element() # optional - sage.combinat + sage: loads(g.dumps()) == g # optional - sage.combinat True """ return (_unpickle_generic_element, (self.parent(), self._matrix,)) @@ -200,8 +200,8 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): EXAMPLES:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: W.an_element() + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: W.an_element() # optional - sage.combinat [ 0 0 -1] [ 1 0 -1] [ 0 1 -1] @@ -212,9 +212,9 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): r""" EXAMPLES:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.an_element() - sage: latex(g) + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.an_element() # optional - sage.combinat + sage: latex(g) # optional - sage.combinat \left(\begin{array}{rrr} 0 & 0 & -1 \\ 1 & 0 & -1 \\ @@ -227,13 +227,13 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): """ EXAMPLES:: - sage: W = CoxeterGroup(['A',4], base_ring=ZZ) - sage: g = W.gen(0) - sage: g * vector([1,1,1,1]) + sage: W = CoxeterGroup(['A',4], base_ring=ZZ) # optional - sage.combinat + sage: g = W.gen(0) # optional - sage.combinat + sage: g * vector([1,1,1,1]) # optional - sage.combinat (0, 1, 1, 1) - sage: v = vector([3,2,1,-1]) - sage: g = W.gen(1) - sage: v * g == v * g.matrix() # indirect doctest + sage: v = vector([3,2,1,-1]) # optional - sage.combinat + sage: g = W.gen(1) # optional - sage.combinat + sage: v * g == v * g.matrix() # indirect doctest # optional - sage.combinat True """ if not is_MatrixGroupElement(x) and x not in self.parent().base_ring(): @@ -249,16 +249,16 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): """ EXAMPLES:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.an_element() - sage: TestSuite(g).run() - sage: h = W.gen(0) * W.gen(1) * W.gen(2) - sage: g == h + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.an_element() # optional - sage.combinat + sage: TestSuite(g).run() # optional - sage.combinat + sage: h = W.gen(0) * W.gen(1) * W.gen(2) # optional - sage.combinat + sage: g == h # optional - sage.combinat True - sage: a = W.gen(0) - sage: a == g + sage: a = W.gen(0) # optional - sage.combinat + sage: a == g # optional - sage.combinat False - sage: a != g + sage: a != g # optional - sage.combinat True """ cdef MatrixGroupElement_generic x = self @@ -271,13 +271,13 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): EXAMPLES:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.gen(0) - sage: g + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.gen(0) # optional - sage.combinat + sage: g # optional - sage.combinat [-1 1 0] [ 0 1 0] [ 0 0 1] - sage: g.list() + sage: g.list() # optional - sage.combinat [[-1, 1, 0], [0, 1, 0], [0, 0, 1]] """ return [r.list() for r in self._matrix.rows()] @@ -292,19 +292,19 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): EXAMPLES:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.gen(0) - sage: g.matrix() + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.gen(0) # optional - sage.combinat + sage: g.matrix() # optional - sage.combinat [-1 1 0] [ 0 1 0] [ 0 0 1] - sage: parent(g.matrix()) + sage: parent(g.matrix()) # optional - sage.combinat Full MatrixSpace of 3 by 3 dense matrices over Integer Ring Matrices have extra functionality that matrix group elements do not have:: - sage: g.matrix().charpoly('t') + sage: g.matrix().charpoly('t') # optional - sage.combinat t^3 - t^2 - t + 1 """ return self._matrix @@ -315,9 +315,9 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): EXAMPLES:: - sage: W = CoxeterGroup(['A', 3], base_ring=ZZ) - sage: g = W.gen(0) - sage: matrix(RDF, g) + sage: W = CoxeterGroup(['A', 3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.gen(0) # optional - sage.combinat + sage: matrix(RDF, g) # optional - sage.combinat [-1.0 1.0 0.0] [ 0.0 1.0 0.0] [ 0.0 0.0 1.0] @@ -331,10 +331,10 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): EXAMPLES:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.gen(0) - sage: h = W.an_element() - sage: g * h + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.gen(0) # optional - sage.combinat + sage: h = W.an_element() # optional - sage.combinat + sage: g * h # optional - sage.combinat [ 1 0 0] [ 1 0 -1] [ 0 1 -1] @@ -352,14 +352,14 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): EXAMPLES:: - sage: W = CoxeterGroup(['A',3]) - sage: g = W.gen(0) - sage: g.is_one() + sage: W = CoxeterGroup(['A',3]) # optional - sage.combinat + sage: g = W.gen(0) # optional - sage.combinat + sage: g.is_one() # optional - sage.combinat False - sage: W.an_element().is_one() + sage: W.an_element().is_one() # optional - sage.combinat False - sage: W.one().is_one() + sage: W.one().is_one() # optional - sage.combinat True """ return self._matrix.is_one() @@ -368,28 +368,26 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): """ Return the inverse group element - OUTPUT: - - A matrix group element. + OUTPUT: A matrix group element. EXAMPLES:: - sage: W = CoxeterGroup(['A',3], base_ring=ZZ) - sage: g = W.an_element() - sage: ~g + sage: W = CoxeterGroup(['A',3], base_ring=ZZ) # optional - sage.combinat + sage: g = W.an_element() # optional - sage.combinat + sage: ~g # optional - sage.combinat [-1 1 0] [-1 0 1] [-1 0 0] - sage: g * ~g == W.one() + sage: g * ~g == W.one() # optional - sage.combinat True - sage: ~g * g == W.one() + sage: ~g * g == W.one() # optional - sage.combinat True - sage: W = CoxeterGroup(['B',3]) - sage: W.base_ring() + sage: W = CoxeterGroup(['B',3]) # optional - sage.combinat sage.rings.number_field + sage: W.base_ring() # optional - sage.combinat sage.rings.number_field Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095? - sage: g = W.an_element() - sage: ~g + sage: g = W.an_element() # optional - sage.combinat sage.rings.number_field + sage: ~g # optional - sage.combinat sage.rings.number_field [-1 1 0] [-1 0 a] [-a 0 1] @@ -409,404 +407,6 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): inverse = __invert__ -################################################################### -# -# Matrix group elements implemented in GAP -# -################################################################### - -cdef class MatrixGroupElement_gap(ElementLibGAP): - """ - Element of a matrix group over a generic ring. - - The group elements are implemented as wrappers around libGAP matrices. - - INPUT: - - - ``M`` -- a matrix - - - ``parent`` -- the parent - - - ``check`` -- bool (default: ``True``); if ``True`` does some - type checking - - - ``convert`` -- bool (default: ``True``); if ``True`` convert - ``M`` to the right matrix space - """ - def __init__(self, parent, M, check=True, convert=True): - r""" - Initialize ``self``. - - TESTS:: - - sage: MS = MatrixSpace(GF(3),2,2) - sage: G = MatrixGroup(MS([[1,0],[0,1]]), MS([[1,1],[0,1]])) - sage: G.gen(0) - [1 0] - [0 1] - sage: g = G.random_element() - sage: TestSuite(g).run() - """ - if isinstance(M, GapElement): - ElementLibGAP.__init__(self, parent, M) - return - if convert: - M = parent.matrix_space()(M) - from sage.libs.gap.libgap import libgap - M_gap = libgap(M) - if check: - if not is_Matrix(M): - raise TypeError('M must be a matrix') - if M.parent() is not parent.matrix_space(): - raise TypeError('M must be a in the matrix space of the group') - parent._check_matrix(M, M_gap) - ElementLibGAP.__init__(self, parent, M_gap) - - def __reduce__(self): - """ - Implement pickling. - - TESTS:: - - sage: MS = MatrixSpace(GF(3), 2, 2) - sage: G = MatrixGroup(MS([[1,0],[0,1]]), MS([[1,1],[0,1]])) - sage: loads(G.gen(0).dumps()) - [1 0] - [0 1] - """ - return (self.parent(), (self.matrix(),)) - - def __hash__(self): - r""" - TESTS:: - - sage: MS = MatrixSpace(GF(3), 2) - sage: G = MatrixGroup([MS([1,1,0,1]), MS([1,0,1,1])]) - sage: g = G.an_element() - sage: hash(g) - -5306160029685893860 # 64-bit - -181258980 # 32-bit - """ - return hash(self.matrix()) - - def _repr_(self): - r""" - Return string representation of this matrix. - - EXAMPLES:: - - sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] - sage: G = MatrixGroup(gens) - sage: g = G([[1, 1], [0, 1]]) - sage: g # indirect doctest - [1 1] - [0 1] - sage: g._repr_() - '[1 1]\n[0 1]' - """ - return str(self.matrix()) - - def _latex_(self): - r""" - EXAMPLES:: - - sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] - sage: G = MatrixGroup(gens) - sage: g = G([[1, 1], [0, 1]]) - sage: print(g._latex_()) - \left(\begin{array}{rr} - 1 & 1 \\ - 0 & 1 - \end{array}\right) - - Type ``view(g._latex_())`` to see the object in an - xdvi window (assuming you have latex and xdvi installed). - """ - return self.matrix()._latex_() - - cpdef _act_on_(self, x, bint self_on_left): - """ - EXAMPLES:: - - sage: G = GL(4,7) - sage: G.0 * vector([1,2,3,4]) - (3, 2, 3, 4) - sage: v = vector(GF(7), [3,2,1,-1]) - sage: g = G.1 - sage: v * g == v * g.matrix() # indirect doctest - True - """ - if not is_MatrixGroupElement(x) and x not in self.parent().base_ring(): - try: - if self_on_left: - return self.matrix() * x - else: - return x * self.matrix() - except TypeError: - return None - - cpdef _richcmp_(self, other, int op): - """ - EXAMPLES:: - - sage: F = GF(3); MS = MatrixSpace(F,2) - sage: gens = [MS([1,0, 0,1]), MS([1,1, 0,1])] - sage: G = MatrixGroup(gens) - sage: g = G([1,1, 0,1]) - sage: h = G([1,1, 0,1]) - sage: g == h - True - sage: g == G.one() - False - """ - return richcmp(self.matrix(), other.matrix(), op) - - @cached_method - def matrix(self): - """ - Obtain the usual matrix (as an element of a matrix space) - associated to this matrix group element. - - EXAMPLES:: - - sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] - sage: G = MatrixGroup(gens) - sage: m = G.gen(0).matrix(); m - [1 0] - [0 1] - sage: m.parent() - Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 3 - - sage: k = GF(7); G = MatrixGroup([matrix(k,2,[1,1,0,1]), matrix(k,2,[1,0,0,2])]) - sage: g = G.0 - sage: g.matrix() - [1 1] - [0 1] - sage: parent(g.matrix()) - Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 7 - - Matrices have extra functionality that matrix group elements - do not have:: - - sage: g.matrix().charpoly('t') - t^2 + 5*t + 1 - """ - # We do a slightly specialized version of sage.libs.gap.element.GapElement.matrix() - # in order to use our current matrix space directly and avoid - # some overhead safety checks. - entries = self.gap().Flat() - MS = self.parent().matrix_space() - ring = MS.base_ring() - m = MS([x.sage(ring=ring) for x in entries]) - m.set_immutable() - return m - - def _matrix_(self, base=None): - """ - Method used by the :func:`matrix` constructor. - - EXAMPLES:: - - sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: G = MatrixGroup([MS([1,1,0,1])]) - sage: g = G.gen(0) - sage: M = matrix(GF(9), g); M; parent(M) - [1 1] - [0 1] - Full MatrixSpace of 2 by 2 dense matrices over Finite Field in z2 of size 3^2 - """ - return self.matrix() - - cpdef list list(self): - """ - Return list representation of this matrix. - - EXAMPLES:: - - sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] - sage: G = MatrixGroup(gens) - sage: g = G.0 - sage: g - [1 0] - [0 1] - sage: g.list() - [[1, 0], [0, 1]] - """ - return [r.list() for r in self.matrix().rows()] - - @cached_method - def multiplicative_order(self): - """ - Return the order of this group element, which is the smallest - positive integer `n` such that `g^n = 1`, or - +Infinity if no such integer exists. - - EXAMPLES:: - - sage: k = GF(7) - sage: G = MatrixGroup([matrix(k,2,[1,1,0,1]), matrix(k,2,[1,0,0,2])]); G - Matrix group over Finite Field of size 7 with 2 generators ( - [1 1] [1 0] - [0 1], [0 2] - ) - sage: G.order() - 21 - sage: G.gen(0).multiplicative_order(), G.gen(1).multiplicative_order() - (7, 3) - - ``order`` is just an alias for ``multiplicative_order``:: - - sage: G.gen(0).order(), G.gen(1).order() - (7, 3) - - sage: k = QQ - sage: G = MatrixGroup([matrix(k,2,[1,1,0,1]), matrix(k,2,[1,0,0,2])]); G - Matrix group over Rational Field with 2 generators ( - [1 1] [1 0] - [0 1], [0 2] - ) - sage: G.order() - +Infinity - sage: G.gen(0).order(), G.gen(1).order() - (+Infinity, +Infinity) - - sage: gl = GL(2, ZZ); gl - General Linear Group of degree 2 over Integer Ring - sage: g = gl.gen(2); g - [1 1] - [0 1] - sage: g.order() - +Infinity - """ - order = self.gap().Order() - if order.IsInt(): - return order.sage() - else: - assert order.IsInfinity() - from sage.rings.infinity import Infinity - return Infinity - - def word_problem(self, gens=None): - r""" - Solve the word problem. - - This method writes the group element as a product of the - elements of the list ``gens``, or the standard generators of - the parent of self if ``gens`` is None. - - INPUT: - - - ``gens`` -- a list/tuple/iterable of elements (or objects - that can be converted to group elements), or ``None`` - (default). By default, the generators of the parent group - are used. - - OUTPUT: - - A factorization object that contains information about the - order of factors and the exponents. A ``ValueError`` is raised - if the group element cannot be written as a word in ``gens``. - - ALGORITHM: - - Use GAP, which has optimized algorithms for solving the word - problem (the GAP functions ``EpimorphismFromFreeGroup`` and - ``PreImagesRepresentative``). - - EXAMPLES:: - - sage: G = GL(2,5); G - General Linear Group of degree 2 over Finite Field of size 5 - sage: G.gens() - ( - [2 0] [4 1] - [0 1], [4 0] - ) - sage: G(1).word_problem([G.gen(0)]) - 1 - sage: type(_) - - - sage: g = G([0,4,1,4]) - sage: g.word_problem() - ([4 1] - [4 0])^-1 - - Next we construct a more complicated element of the group from the - generators:: - - sage: s,t = G.0, G.1 - sage: a = (s * t * s); b = a.word_problem(); b - ([2 0] - [0 1]) * - ([4 1] - [4 0]) * - ([2 0] - [0 1]) - sage: flatten(b) - [ - [2 0] [4 1] [2 0] - [0 1], 1, [4 0], 1, [0 1], 1 - ] - sage: b.prod() == a - True - - We solve the word problem using some different generators:: - - sage: s = G([2,0,0,1]); t = G([1,1,0,1]); u = G([0,-1,1,0]) - sage: a.word_problem([s,t,u]) - ([2 0] - [0 1])^-1 * - ([1 1] - [0 1])^-1 * - ([0 4] - [1 0]) * - ([2 0] - [0 1])^-1 - - We try some elements that don't actually generate the group:: - - sage: a.word_problem([t,u]) - Traceback (most recent call last): - ... - ValueError: word problem has no solution - - AUTHORS: - - - David Joyner and William Stein - - David Loeffler (2010): fixed some bugs - - Volker Braun (2013): LibGAP - """ - from sage.libs.gap.libgap import libgap - G = self.parent() - if gens: - gen = lambda i:gens[i] - H = libgap.Group([G(x).gap() for x in gens]) - else: - gen = G.gen - H = G.gap() - hom = H.EpimorphismFromFreeGroup() - preimg = hom.PreImagesRepresentative(self.gap()) - - if preimg.is_bool(): - assert preimg == libgap.eval('fail') - raise ValueError('word problem has no solution') - - result = [] - n = preimg.NumberSyllables().sage() - exponent_syllable = libgap.eval('ExponentSyllable') - generator_syllable = libgap.eval('GeneratorSyllable') - for i in range(n): - exponent = exponent_syllable(preimg, i+1).sage() - generator = gen(generator_syllable(preimg, i+1).sage() - 1) - result.append( (generator, exponent) ) - result = Factorization(result) - result._set_cr(True) - return result def _unpickle_generic_element(G, mat): """ @@ -814,12 +414,12 @@ def _unpickle_generic_element(G, mat): EXAMPLES:: - sage: m1 = matrix(SR, [[1,2],[3,4]]) - sage: m2 = matrix(SR, [[1,3],[-1,0]]) - sage: G = MatrixGroup(m1, m2) - sage: m = G.an_element() - sage: from sage.groups.matrix_gps.group_element import _unpickle_generic_element - sage: _unpickle_generic_element(G, m.matrix()) == m + sage: m1 = matrix(SR, [[1,2], [3,4]]) # optional - sage.symbolic + sage: m2 = matrix(SR, [[1,3], [-1,0]]) # optional - sage.symbolic + sage: G = MatrixGroup(m1, m2) # optional - sage.symbolic + sage: m = G.an_element() # optional - sage.symbolic + sage: from sage.groups.matrix_gps.group_element import _unpickle_generic_element # optional - sage.symbolic + sage: _unpickle_generic_element(G, m.matrix()) == m # optional - sage.symbolic True """ return G.element_class(G, mat, False, False) diff --git a/src/sage/groups/matrix_gps/group_element_gap.pxd b/src/sage/groups/matrix_gps/group_element_gap.pxd new file mode 100644 index 00000000000..e0ecbefea5c --- /dev/null +++ b/src/sage/groups/matrix_gps/group_element_gap.pxd @@ -0,0 +1,5 @@ +from sage.groups.libgap_wrapper cimport ElementLibGAP + +cdef class MatrixGroupElement_gap(ElementLibGAP): + cpdef _act_on_(self, x, bint self_on_left) + cpdef list list(self) diff --git a/src/sage/groups/matrix_gps/group_element_gap.pyx b/src/sage/groups/matrix_gps/group_element_gap.pyx new file mode 100644 index 00000000000..7aba88e9b88 --- /dev/null +++ b/src/sage/groups/matrix_gps/group_element_gap.pyx @@ -0,0 +1,419 @@ +r""" +Matrix group elements implemented in GAP +""" + +#***************************************************************************** +# Copyright (C) 2006 David Joyner and William Stein +# 2013 Volker Braun +# 2015-2017 Vincent Delecroix +# 2016 Travis Scrimshaw +# 2018 Jeroen Demeyer +# 2023 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.groups.matrix_gps.group_element cimport is_MatrixGroupElement +from sage.libs.gap.element cimport GapElement, GapElement_List +from sage.misc.cachefunc import cached_method +from sage.structure.element import is_Matrix +from sage.structure.factorization import Factorization +from sage.structure.richcmp cimport richcmp + + +cdef class MatrixGroupElement_gap(ElementLibGAP): + """ + Element of a matrix group over a generic ring. + + The group elements are implemented as wrappers around libGAP matrices. + + INPUT: + + - ``M`` -- a matrix + + - ``parent`` -- the parent + + - ``check`` -- bool (default: ``True``); if ``True``, do some + type checking + + - ``convert`` -- bool (default: ``True``); if ``True``, convert + ``M`` to the right matrix space + """ + def __init__(self, parent, M, check=True, convert=True): + r""" + Initialize ``self``. + + TESTS:: + + sage: MS = MatrixSpace(GF(3),2,2) + sage: G = MatrixGroup(MS([[1,0],[0,1]]), MS([[1,1],[0,1]])) + sage: G.gen(0) + [1 0] + [0 1] + sage: g = G.random_element() + sage: TestSuite(g).run() + """ + if isinstance(M, GapElement): + ElementLibGAP.__init__(self, parent, M) + return + if convert: + M = parent.matrix_space()(M) + from sage.libs.gap.libgap import libgap + M_gap = libgap(M) + if check: + if not is_Matrix(M): + raise TypeError('M must be a matrix') + if M.parent() is not parent.matrix_space(): + raise TypeError('M must be a in the matrix space of the group') + parent._check_matrix(M, M_gap) + ElementLibGAP.__init__(self, parent, M_gap) + + def __reduce__(self): + """ + Implement pickling. + + TESTS:: + + sage: MS = MatrixSpace(GF(3), 2, 2) + sage: G = MatrixGroup(MS([[1,0],[0,1]]), MS([[1,1],[0,1]])) + sage: loads(G.gen(0).dumps()) + [1 0] + [0 1] + """ + return (self.parent(), (self.matrix(),)) + + def __hash__(self): + r""" + TESTS:: + + sage: MS = MatrixSpace(GF(3), 2) + sage: G = MatrixGroup([MS([1,1,0,1]), MS([1,0,1,1])]) + sage: g = G.an_element() + sage: hash(g) + -5306160029685893860 # 64-bit + -181258980 # 32-bit + """ + return hash(self.matrix()) + + def _repr_(self): + r""" + Return string representation of this matrix. + + EXAMPLES:: + + sage: F = GF(3); MS = MatrixSpace(F,2,2) + sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] + sage: G = MatrixGroup(gens) + sage: g = G([[1, 1], [0, 1]]) + sage: g # indirect doctest + [1 1] + [0 1] + sage: g._repr_() + '[1 1]\n[0 1]' + """ + return str(self.matrix()) + + def _latex_(self): + r""" + EXAMPLES:: + + sage: F = GF(3); MS = MatrixSpace(F,2,2) + sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] + sage: G = MatrixGroup(gens) + sage: g = G([[1, 1], [0, 1]]) + sage: print(g._latex_()) + \left(\begin{array}{rr} + 1 & 1 \\ + 0 & 1 + \end{array}\right) + + Type ``view(g._latex_())`` to see the object in an + xdvi window (assuming you have latex and xdvi installed). + """ + return self.matrix()._latex_() + + cpdef _act_on_(self, x, bint self_on_left): + """ + EXAMPLES:: + + sage: G = GL(4,7) + sage: G.0 * vector([1,2,3,4]) + (3, 2, 3, 4) + sage: v = vector(GF(7), [3,2,1,-1]) + sage: g = G.1 + sage: v * g == v * g.matrix() # indirect doctest + True + """ + if not is_MatrixGroupElement(x) and x not in self.parent().base_ring(): + try: + if self_on_left: + return self.matrix() * x + else: + return x * self.matrix() + except TypeError: + return None + + cpdef _richcmp_(self, other, int op): + """ + EXAMPLES:: + + sage: F = GF(3); MS = MatrixSpace(F,2) + sage: gens = [MS([1,0, 0,1]), MS([1,1, 0,1])] + sage: G = MatrixGroup(gens) + sage: g = G([1,1, 0,1]) + sage: h = G([1,1, 0,1]) + sage: g == h + True + sage: g == G.one() + False + """ + return richcmp(self.matrix(), other.matrix(), op) + + @cached_method + def matrix(self): + """ + Obtain the usual matrix (as an element of a matrix space) + associated to this matrix group element. + + EXAMPLES:: + + sage: F = GF(3); MS = MatrixSpace(F,2,2) + sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] + sage: G = MatrixGroup(gens) + sage: m = G.gen(0).matrix(); m + [1 0] + [0 1] + sage: m.parent() + Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 3 + + sage: k = GF(7); G = MatrixGroup([matrix(k,2,[1,1,0,1]), matrix(k,2,[1,0,0,2])]) + sage: g = G.0 + sage: g.matrix() + [1 1] + [0 1] + sage: parent(g.matrix()) + Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 7 + + Matrices have extra functionality that matrix group elements + do not have:: + + sage: g.matrix().charpoly('t') + t^2 + 5*t + 1 + """ + # We do a slightly specialized version of sage.libs.gap.element.GapElement.matrix() + # in order to use our current matrix space directly and avoid + # some overhead safety checks. + entries = self.gap().Flat() + MS = self.parent().matrix_space() + ring = MS.base_ring() + m = MS([x.sage(ring=ring) for x in entries]) + m.set_immutable() + return m + + def _matrix_(self, base=None): + """ + Method used by the :func:`matrix` constructor. + + EXAMPLES:: + + sage: F = GF(3); MS = MatrixSpace(F,2,2) + sage: G = MatrixGroup([MS([1,1,0,1])]) + sage: g = G.gen(0) + sage: M = matrix(GF(9), g); M; parent(M) + [1 1] + [0 1] + Full MatrixSpace of 2 by 2 dense matrices over Finite Field in z2 of size 3^2 + """ + return self.matrix() + + cpdef list list(self): + """ + Return list representation of this matrix. + + EXAMPLES:: + + sage: F = GF(3); MS = MatrixSpace(F,2,2) + sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] + sage: G = MatrixGroup(gens) + sage: g = G.0 + sage: g + [1 0] + [0 1] + sage: g.list() + [[1, 0], [0, 1]] + """ + return [r.list() for r in self.matrix().rows()] + + @cached_method + def multiplicative_order(self): + """ + Return the order of this group element, which is the smallest + positive integer `n` such that `g^n = 1`, or + +Infinity if no such integer exists. + + EXAMPLES:: + + sage: k = GF(7) + sage: G = MatrixGroup([matrix(k,2,[1,1,0,1]), matrix(k,2,[1,0,0,2])]); G + Matrix group over Finite Field of size 7 with 2 generators ( + [1 1] [1 0] + [0 1], [0 2] + ) + sage: G.order() + 21 + sage: G.gen(0).multiplicative_order(), G.gen(1).multiplicative_order() + (7, 3) + + ``order`` is just an alias for ``multiplicative_order``:: + + sage: G.gen(0).order(), G.gen(1).order() + (7, 3) + + sage: k = QQ + sage: G = MatrixGroup([matrix(k,2,[1,1,0,1]), matrix(k,2,[1,0,0,2])]); G + Matrix group over Rational Field with 2 generators ( + [1 1] [1 0] + [0 1], [0 2] + ) + sage: G.order() + +Infinity + sage: G.gen(0).order(), G.gen(1).order() + (+Infinity, +Infinity) + + sage: gl = GL(2, ZZ); gl + General Linear Group of degree 2 over Integer Ring + sage: g = gl.gen(2); g + [1 1] + [0 1] + sage: g.order() + +Infinity + """ + order = self.gap().Order() + if order.IsInt(): + return order.sage() + else: + assert order.IsInfinity() + from sage.rings.infinity import Infinity + return Infinity + + def word_problem(self, gens=None): + r""" + Solve the word problem. + + This method writes the group element as a product of the + elements of the list ``gens``, or the standard generators of + the parent of self if ``gens`` is None. + + INPUT: + + - ``gens`` -- a list/tuple/iterable of elements (or objects + that can be converted to group elements), or ``None`` + (default). By default, the generators of the parent group + are used. + + OUTPUT: + + A factorization object that contains information about the + order of factors and the exponents. A :class:`ValueError` is raised + if the group element cannot be written as a word in ``gens``. + + ALGORITHM: + + Use GAP, which has optimized algorithms for solving the word + problem (the GAP functions ``EpimorphismFromFreeGroup`` and + ``PreImagesRepresentative``). + + EXAMPLES:: + + sage: G = GL(2,5); G + General Linear Group of degree 2 over Finite Field of size 5 + sage: G.gens() + ( + [2 0] [4 1] + [0 1], [4 0] + ) + sage: G(1).word_problem([G.gen(0)]) + 1 + sage: type(_) + + + sage: g = G([0,4,1,4]) + sage: g.word_problem() + ([4 1] + [4 0])^-1 + + Next we construct a more complicated element of the group from the + generators:: + + sage: s,t = G.0, G.1 + sage: a = (s * t * s); b = a.word_problem(); b + ([2 0] + [0 1]) * + ([4 1] + [4 0]) * + ([2 0] + [0 1]) + sage: flatten(b) + [ + [2 0] [4 1] [2 0] + [0 1], 1, [4 0], 1, [0 1], 1 + ] + sage: b.prod() == a + True + + We solve the word problem using some different generators:: + + sage: s = G([2,0,0,1]); t = G([1,1,0,1]); u = G([0,-1,1,0]) + sage: a.word_problem([s,t,u]) + ([2 0] + [0 1])^-1 * + ([1 1] + [0 1])^-1 * + ([0 4] + [1 0]) * + ([2 0] + [0 1])^-1 + + We try some elements that don't actually generate the group:: + + sage: a.word_problem([t,u]) + Traceback (most recent call last): + ... + ValueError: word problem has no solution + + AUTHORS: + + - David Joyner and William Stein + - David Loeffler (2010): fixed some bugs + - Volker Braun (2013): LibGAP + """ + from sage.libs.gap.libgap import libgap + G = self.parent() + if gens: + gen = lambda i:gens[i] + H = libgap.Group([G(x).gap() for x in gens]) + else: + gen = G.gen + H = G.gap() + hom = H.EpimorphismFromFreeGroup() + preimg = hom.PreImagesRepresentative(self.gap()) + + if preimg.is_bool(): + assert preimg == libgap.eval('fail') + raise ValueError('word problem has no solution') + + result = [] + n = preimg.NumberSyllables().sage() + exponent_syllable = libgap.eval('ExponentSyllable') + generator_syllable = libgap.eval('GeneratorSyllable') + for i in range(n): + exponent = exponent_syllable(preimg, i+1).sage() + generator = gen(generator_syllable(preimg, i+1).sage() - 1) + result.append( (generator, exponent) ) + result = Factorization(result) + result._set_cr(True) + return result diff --git a/src/sage/groups/matrix_gps/heisenberg.py b/src/sage/groups/matrix_gps/heisenberg.py index 68832525331..a3b74081750 100644 --- a/src/sage/groups/matrix_gps/heisenberg.py +++ b/src/sage/groups/matrix_gps/heisenberg.py @@ -16,7 +16,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap from sage.structure.unique_representation import UniqueRepresentation from sage.misc.latex import latex from sage.matrix.matrix_space import MatrixSpace diff --git a/src/sage/groups/matrix_gps/isometries.py b/src/sage/groups/matrix_gps/isometries.py index cca45e71752..3810a80cadc 100644 --- a/src/sage/groups/matrix_gps/isometries.py +++ b/src/sage/groups/matrix_gps/isometries.py @@ -1,5 +1,5 @@ r""" -Groups of isometries. +Groups of isometries Let `M = \ZZ^n` or `\QQ^n`, `b: M \times M \rightarrow \QQ` a bilinear form and `f: M \rightarrow M` a linear map. We say that `f` is an isometry if for all @@ -39,7 +39,7 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap from sage.categories.action import Action @@ -68,9 +68,9 @@ class GroupOfIsometries(FinitelyGeneratedMatrixGroup_gap): EXAMPLES:: sage: from sage.groups.matrix_gps.isometries import GroupOfIsometries - sage: bil = Matrix(ZZ,2,[3,2,2,3]) - sage: gens = [-Matrix(ZZ,2,[0,1,1,0])] - sage: O = GroupOfIsometries(2,ZZ,gens,bil) + sage: bil = Matrix(ZZ, 2, [3,2,2,3]) + sage: gens = [-Matrix(ZZ, 2, [0,1,1,0])] + sage: O = GroupOfIsometries(2, ZZ, gens, bil) sage: O Group of isometries with 1 generator ( [ 0 -1] @@ -83,7 +83,7 @@ class GroupOfIsometries(FinitelyGeneratedMatrixGroup_gap): sage: bil = Matrix(ZZ,4,[0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0]) sage: f = Matrix(ZZ,4,[0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1, 1, 1, 1]) - sage: O = GroupOfIsometries(2,ZZ,[f],bil) + sage: O = GroupOfIsometries(2, ZZ, [f], bil) sage: O.cardinality() +Infinity """ @@ -135,9 +135,7 @@ def _repr_(self): r""" Return the string representation of this matrix group. - OUTPUT: - - - a string + OUTPUT: a string EXAMPLES:: @@ -186,9 +184,7 @@ def invariant_bilinear_form(self): r""" Return the symmetric bilinear form preserved by the orthogonal group. - OUTPUT: - - - the matrix defining the bilinear form + OUTPUT: the matrix defining the bilinear form EXAMPLES:: @@ -278,9 +274,11 @@ class GroupActionOnSubmodule(Action): EXAMPLES:: sage: from sage.groups.matrix_gps.isometries import GroupOfIsometries - sage: S = span(ZZ,[[0,1]]) - sage: g = Matrix(QQ,2,[1,0,0,-1]) - sage: G = GroupOfIsometries(2, ZZ, [g], invariant_bilinear_form=matrix.identity(2), invariant_submodule=S) + sage: S = span(ZZ, [[0,1]]) + sage: g = Matrix(QQ, 2, [1,0,0,-1]) + sage: G = GroupOfIsometries(2, ZZ, [g], + ....: invariant_bilinear_form=matrix.identity(2), + ....: invariant_submodule=S) sage: g = G.an_element() sage: x = S.an_element() sage: x*g @@ -297,11 +295,11 @@ def __init__(self, MatrixGroup,submodule, is_left=False): TESTS:: sage: from sage.groups.matrix_gps.isometries import GroupOfIsometries, GroupActionOnSubmodule - sage: S = span(ZZ,[[0,1]]) - sage: g = Matrix(QQ,2,[1,0,0,-1]) + sage: S = span(ZZ, [[0,1]]) + sage: g = Matrix(QQ, 2, [1,0,0,-1]) sage: e = Matrix.identity(2) sage: G = GroupOfIsometries(2, ZZ, [g], e) - sage: GroupActionOnSubmodule(G,S) + sage: GroupActionOnSubmodule(G, S) Right action by Group of isometries with 1 generator ( [ 1 0] [ 0 -1] @@ -406,9 +404,7 @@ def _act_(self, g, a): - ``a`` -- an element of the invariant submodule - OUTPUT: - - - an element of the invariant quotient module + OUTPUT: an element of the invariant quotient module EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/linear.py b/src/sage/groups/matrix_gps/linear.py index 98f416a738e..8dc29267923 100644 --- a/src/sage/groups/matrix_gps/linear.py +++ b/src/sage/groups/matrix_gps/linear.py @@ -3,28 +3,28 @@ EXAMPLES:: - sage: GL(4,QQ) + sage: GL(4, QQ) General Linear Group of degree 4 over Rational Field - sage: GL(1,ZZ) + sage: GL(1, ZZ) General Linear Group of degree 1 over Integer Ring - sage: GL(100,RR) + sage: GL(100, RR) General Linear Group of degree 100 over Real Field with 53 bits of precision - sage: GL(3,GF(49,'a')) + sage: GL(3, GF(49,'a')) # optional - sage.rings.finite_rings General Linear Group of degree 3 over Finite Field in a of size 7^2 sage: SL(2, ZZ) Special Linear Group of degree 2 over Integer Ring - sage: G = SL(2,GF(3)); G + sage: G = SL(2, GF(3)); G # optional - sage.rings.finite_rings Special Linear Group of degree 2 over Finite Field of size 3 - sage: G.is_finite() + sage: G.is_finite() # optional - sage.rings.finite_rings True - sage: G.conjugacy_classes_representatives() + sage: G.conjugacy_classes_representatives() # optional - sage.rings.finite_rings ( [1 0] [0 2] [0 1] [2 0] [0 2] [0 1] [0 2] [0 1], [1 1], [2 1], [0 2], [1 2], [2 2], [1 0] ) - sage: G = SL(6,GF(5)) - sage: G.gens() + sage: G = SL(6, GF(5)) # optional - sage.rings.finite_rings + sage: G.gens() # optional - sage.rings.finite_rings ( [2 0 0 0 0 0] [4 0 0 0 0 1] [0 3 0 0 0 0] [4 0 0 0 0 0] @@ -59,12 +59,11 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.misc.latex import latex -from sage.groups.matrix_gps.named_group import ( - normalize_args_vectorspace, NamedMatrixGroup_generic, NamedMatrixGroup_gap ) from sage.categories.fields import Fields from sage.categories.groups import Groups -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.named_group import ( + normalize_args_vectorspace, NamedMatrixGroup_generic) +from sage.misc.latex import latex ############################################################################### @@ -94,14 +93,14 @@ def GL(n, R, var='a'): EXAMPLES:: - sage: G = GL(6,GF(5)) - sage: G.order() + sage: G = GL(6, GF(5)) # optional - sage.rings.finite_rings + sage: G.order() # optional - sage.rings.finite_rings 11064475422000000000000000 - sage: G.base_ring() + sage: G.base_ring() # optional - sage.rings.finite_rings Finite Field of size 5 - sage: G.category() + sage: G.category() # optional - sage.rings.finite_rings Category of finite groups - sage: TestSuite(G).run() + sage: TestSuite(G).run() # optional - sage.rings.finite_rings sage: G = GL(6, QQ) sage: G.category() @@ -110,33 +109,34 @@ def GL(n, R, var='a'): Here is the Cayley graph of (relatively small) finite General Linear Group:: - sage: g = GL(2,3) - sage: d = g.cayley_graph(); d + sage: g = GL(2,3) # optional - sage.rings.finite_rings + sage: d = g.cayley_graph(); d # optional - sage.graphs sage.rings.finite_rings Digraph on 48 vertices - sage: d.plot(color_by_label=True, vertex_size=0.03, vertex_labels=False) # long time + sage: d.plot(color_by_label=True, vertex_size=0.03, # long time # optional - sage.graphs sage.rings.finite_rings sage.plot + ....: vertex_labels=False) Graphics object consisting of 144 graphics primitives - sage: d.plot3d(color_by_label=True) # long time + sage: d.plot3d(color_by_label=True) # long time # optional - sage.graphs sage.rings.finite_rings sage.plot Graphics3d Object :: - sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[2,0],[0,1]]), MS([[2,1],[2,0]])] - sage: G = MatrixGroup(gens) - sage: G.order() + sage: F = GF(3); MS = MatrixSpace(F, 2, 2) # optional - sage.rings.finite_rings + sage: gens = [MS([[2,0], [0,1]]), MS([[2,1], [2,0]])] # optional - sage.rings.finite_rings + sage: G = MatrixGroup(gens) # optional - sage.rings.finite_rings + sage: G.order() # optional - sage.rings.finite_rings 48 - sage: G.cardinality() + sage: G.cardinality() # optional - sage.rings.finite_rings 48 - sage: H = GL(2,F) - sage: H.order() + sage: H = GL(2,F) # optional - sage.rings.finite_rings + sage: H.order() # optional - sage.rings.finite_rings 48 - sage: H == G + sage: H == G # optional - sage.rings.finite_rings True - sage: H.gens() == G.gens() + sage: H.gens() == G.gens() # optional - sage.rings.finite_rings True - sage: H.as_matrix_group() == H + sage: H.as_matrix_group() == H # optional - sage.rings.finite_rings True - sage: H.gens() + sage: H.gens() # optional - sage.rings.finite_rings ( [2 0] [2 1] [0 1], [2 0] @@ -144,11 +144,11 @@ def GL(n, R, var='a'): TESTS:: - sage: groups.matrix.GL(2, 3) + sage: groups.matrix.GL(2, 3) # optional - sage.groups sage.rings.finite_rings General Linear Group of degree 2 over Finite Field of size 3 - sage: groups.matrix.GL(1, ZZ).category() + sage: groups.matrix.GL(1, ZZ).category() # optional - sage.groups Category of groups - sage: groups.matrix.GL(1, QQ).category() + sage: groups.matrix.GL(1, QQ).category() # optional - sage.groups Category of infinite groups """ degree, ring = normalize_args_vectorspace(n, R, var='a') @@ -164,12 +164,19 @@ def GL(n, R, var='a'): name = 'General Linear Group of degree {0} over {1}'.format(degree, ring) ltx = 'GL({0}, {1})'.format(degree, latex(ring)) try: - cmd = 'GL({0}, {1})'.format(degree, ring._gap_init_()) - return LinearMatrixGroup_gap(degree, ring, False, name, ltx, cmd, - category=cat) - except ValueError: - return LinearMatrixGroup_generic(degree, ring, False, name, ltx, + from .linear_gap import LinearMatrixGroup_gap + except ImportError: + pass + else: + try: + cmd = 'GL({0}, {1})'.format(degree, ring._gap_init_()) + return LinearMatrixGroup_gap(degree, ring, False, name, ltx, cmd, category=cat) + except ValueError: + pass + + return LinearMatrixGroup_generic(degree, ring, False, name, ltx, + category=cat) ############################################################################### @@ -188,7 +195,7 @@ def SL(n, R, var='a'): This group is also available via ``groups.matrix.SL()``. - INPUT: + INPUT: - ``n`` -- a positive integer. @@ -200,15 +207,15 @@ def SL(n, R, var='a'): EXAMPLES:: - sage: SL(3, GF(2)) + sage: SL(3, GF(2)) # optional - sage.rings.finite_rings Special Linear Group of degree 3 over Finite Field of size 2 - sage: G = SL(15, GF(7)); G + sage: G = SL(15, GF(7)); G # optional - sage.rings.finite_rings Special Linear Group of degree 15 over Finite Field of size 7 - sage: G.category() + sage: G.category() # optional - sage.rings.finite_rings Category of finite groups - sage: G.order() + sage: G.order() # optional - sage.rings.finite_rings 1956712595698146962015219062429586341124018007182049478916067369638713066737882363393519966343657677430907011270206265834819092046250232049187967718149558134226774650845658791865745408000000 - sage: len(G.gens()) + sage: len(G.gens()) # optional - sage.rings.finite_rings 2 sage: G = SL(2, ZZ); G Special Linear Group of degree 2 over Integer Ring @@ -222,7 +229,7 @@ def SL(n, R, var='a'): Next we compute generators for `\mathrm{SL}_3(\ZZ)` :: - sage: G = SL(3,ZZ); G + sage: G = SL(3, ZZ); G Special Linear Group of degree 3 over Integer Ring sage: G.gens() ( @@ -248,12 +255,19 @@ def SL(n, R, var='a'): name = 'Special Linear Group of degree {0} over {1}'.format(degree, ring) ltx = 'SL({0}, {1})'.format(degree, latex(ring)) try: - cmd = 'SL({0}, {1})'.format(degree, ring._gap_init_()) - return LinearMatrixGroup_gap(degree, ring, True, name, ltx, cmd, - category=cat) - except ValueError: - return LinearMatrixGroup_generic(degree, ring, True, name, ltx, + from .linear_gap import LinearMatrixGroup_gap + except ImportError: + pass + else: + try: + cmd = 'SL({0}, {1})'.format(degree, ring._gap_init_()) + return LinearMatrixGroup_gap(degree, ring, True, name, ltx, cmd, category=cat) + except ValueError: + pass + + return LinearMatrixGroup_generic(degree, ring, True, name, ltx, + category=cat) ######################################################################## @@ -263,7 +277,7 @@ def SL(n, R, var='a'): class LinearMatrixGroup_generic(NamedMatrixGroup_generic): def _check_matrix(self, x, *args): - """a + r""" Check whether the matrix ``x`` is special linear. See :meth:`~sage.groups.matrix_gps.matrix_group._check_matrix` @@ -271,8 +285,8 @@ def _check_matrix(self, x, *args): EXAMPLES:: - sage: G = SL(2,GF(5)) - sage: G._check_matrix(G.an_element().matrix()) + sage: G = SL(2, GF(5)) # optional - sage.rings.finite_rings + sage: G._check_matrix(G.an_element().matrix()) # optional - sage.rings.finite_rings """ if self._special: if x.determinant() != 1: @@ -280,19 +294,3 @@ def _check_matrix(self, x, *args): else: if x.determinant() == 0: raise TypeError('matrix must non-zero determinant') - - -class LinearMatrixGroup_gap(NamedMatrixGroup_gap, LinearMatrixGroup_generic, FinitelyGeneratedMatrixGroup_gap): - r""" - The general or special linear group in GAP. - - TESTS: - - Check that :trac:`20867` is fixed:: - - sage: from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap - sage: G = GL(3,3) - sage: isinstance(G, FinitelyGeneratedMatrixGroup_gap) - True - """ - pass diff --git a/src/sage/groups/matrix_gps/linear_gap.py b/src/sage/groups/matrix_gps/linear_gap.py new file mode 100644 index 00000000000..37d33d5d2be --- /dev/null +++ b/src/sage/groups/matrix_gps/linear_gap.py @@ -0,0 +1,23 @@ +""" +Linear Groups with GAP +""" + +from sage.groups.matrix_gps.linear import LinearMatrixGroup_generic +from sage.groups.matrix_gps.named_group_gap import NamedMatrixGroup_gap +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap + + +class LinearMatrixGroup_gap(NamedMatrixGroup_gap, LinearMatrixGroup_generic, FinitelyGeneratedMatrixGroup_gap): + r""" + The general or special linear group in GAP. + + TESTS: + + Check that :trac:`20867` is fixed:: + + sage: from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap + sage: G = GL(3,3) + sage: isinstance(G, FinitelyGeneratedMatrixGroup_gap) + True + """ + pass diff --git a/src/sage/groups/matrix_gps/matrix_group.py b/src/sage/groups/matrix_gps/matrix_group.py index 8cea6a6ef66..526c86695b0 100644 --- a/src/sage/groups/matrix_gps/matrix_group.py +++ b/src/sage/groups/matrix_gps/matrix_group.py @@ -1,16 +1,18 @@ """ Base classes for Matrix Groups +TESTS: + Loading, saving, ... works:: - sage: G = GL(2,5); G + sage: G = GL(2,5); G # optional - sage.rings.finite_rings General Linear Group of degree 2 over Finite Field of size 5 - sage: TestSuite(G).run() + sage: TestSuite(G).run() # optional - sage.rings.finite_rings - sage: g = G.1; g + sage: g = G.1; g # optional - sage.rings.finite_rings [4 1] [4 0] - sage: TestSuite(g).run() + sage: TestSuite(g).run() # optional - sage.rings.finite_rings We test that :trac:`9437` is fixed:: @@ -41,6 +43,12 @@ # **************************************************************************** # Copyright (C) 2006 David Joyner and William Stein +# 2009 Mike Hansen +# 2013 Volker Braun +# 2017-2021 Frédéric Chapoton +# 2018-2019 Sebastian Oehms +# 2020 Siddharth Singh +# 2023 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -58,11 +66,8 @@ richcmp_method, richcmp) from sage.misc.cachefunc import cached_method from sage.groups.group import Group -from sage.groups.libgap_wrapper import ParentLibGAP -from sage.groups.libgap_mixin import GroupMixinLibGAP -from sage.groups.matrix_gps.group_element import ( - MatrixGroupElement_generic, MatrixGroupElement_gap) +from sage.groups.matrix_gps.group_element import MatrixGroupElement_generic def is_MatrixGroup(x): @@ -72,13 +77,13 @@ def is_MatrixGroup(x): EXAMPLES:: sage: from sage.groups.matrix_gps.matrix_group import is_MatrixGroup - sage: is_MatrixGroup(MatrixSpace(QQ,3)) + sage: is_MatrixGroup(MatrixSpace(QQ, 3)) False - sage: is_MatrixGroup(Mat(QQ,3)) + sage: is_MatrixGroup(Mat(QQ, 3)) False - sage: is_MatrixGroup(GL(2,ZZ)) + sage: is_MatrixGroup(GL(2, ZZ)) True - sage: is_MatrixGroup(MatrixGroup([matrix(2,[1,1,0,1])])) + sage: is_MatrixGroup(MatrixGroup([matrix(2, [1,1,0,1])])) True """ return isinstance(x, MatrixGroup_base) @@ -102,9 +107,9 @@ class MatrixGroup_base(Group): TESTS:: - sage: G = SO(3, GF(11)); G + sage: G = SO(3, GF(11)); G # optional - sage.rings.finite_rings Special Orthogonal Group of degree 3 over Finite Field of size 11 - sage: G.category() + sage: G.category() # optional - sage.rings.finite_rings Category of finite groups """ _ambient = None # internal attribute to register the ambient group in case this instance is a subgroup @@ -132,9 +137,9 @@ def _check_matrix(self, x, *args): EXAMPLES:: - sage: G = SU(2,GF(5)); F = G.base_ring() # this is GF(5^2,'a') - sage: G._check_matrix(identity_matrix(F,2)) - sage: G._check_matrix(matrix(F,[[1,1],[0,1]])) + sage: G = SU(2, GF(5)); F = G.base_ring() # this is GF(5^2,'a') # optional - sage.rings.finite_rings + sage: G._check_matrix(identity_matrix(F, 2)) # optional - sage.rings.finite_rings + sage: G._check_matrix(matrix(F, [[1,1], [0,1]])) # optional - sage.rings.finite_rings Traceback (most recent call last): ... TypeError: matrix must be unitary with respect to the hermitian form @@ -153,8 +158,8 @@ def as_matrix_group(self): EXAMPLES:: - sage: G = SU(4,GF(5)) - sage: G.as_matrix_group() + sage: G = SU(4, GF(5)) # optional - sage.rings.finite_rings + sage: G.as_matrix_group() # optional - sage.rings.finite_rings Matrix group over Finite Field in a of size 5^2 with 2 generators ( [ a 0 0 0] [ 1 0 4*a + 3 0] [ 0 2*a + 3 0 0] [ 1 0 0 0] @@ -162,8 +167,8 @@ def as_matrix_group(self): [ 0 0 0 3*a], [ 0 3*a + 1 0 0] ) - sage: G = GO(3,GF(5)) - sage: G.as_matrix_group() + sage: G = GO(3,GF(5)) # optional - sage.rings.finite_rings + sage: G.as_matrix_group() # optional - sage.rings.finite_rings Matrix group over Finite Field of size 5 with 2 generators ( [2 0 0] [0 1 0] [0 3 0] [1 4 4] @@ -179,29 +184,29 @@ def subgroup(self, generators, check=True): INPUT: - - ``generators`` -- a list/tuple/iterable of group elements of self + - ``generators`` -- a list/tuple/iterable of group elements of ``self`` - ``check`` -- boolean (optional, default: ``True``). Whether to check that each matrix is invertible. - OUTPUT: The subgroup generated by ``generators`` as an instance of FinitelyGeneratedMatrixGroup_gap + OUTPUT: The subgroup generated by ``generators`` as an instance of :class:`FinitelyGeneratedMatrixGroup_gap` EXAMPLES:: - sage: UCF = UniversalCyclotomicField() - sage: G = GL(3, UCF) - sage: e3 = UCF.gen(3); e5 =UCF.gen(5) - sage: m = matrix(UCF, 3,3, [[e3, 1, 0], [0, e5, 7],[4, 3, 2]]) - sage: S = G.subgroup([m]); S + sage: UCF = UniversalCyclotomicField() # optional - sage.rings.number_field + sage: G = GL(3, UCF) # optional - sage.rings.number_field + sage: e3 = UCF.gen(3); e5 = UCF.gen(5) # optional - sage.rings.number_field + sage: m = matrix(UCF, 3,3, [[e3, 1, 0], [0, e5, 7],[4, 3, 2]]) # optional - sage.rings.number_field + sage: S = G.subgroup([m]); S # optional - sage.rings.number_field Subgroup with 1 generators ( [E(3) 1 0] [ 0 E(5) 7] [ 4 3 2] ) of General Linear Group of degree 3 over Universal Cyclotomic Field - sage: CF3 = CyclotomicField(3) - sage: G = GL(3, CF3) - sage: e3 = CF3.gen() - sage: m = matrix(CF3, 3,3, [[e3, 1, 0], [0, ~e3, 7],[4, 3, 2]]) - sage: S = G.subgroup([m]); S + sage: CF3 = CyclotomicField(3) # optional - sage.rings.number_field + sage: G = GL(3, CF3) # optional - sage.rings.number_field + sage: e3 = CF3.gen() # optional - sage.rings.number_field + sage: m = matrix(CF3, 3,3, [[e3, 1, 0], [0, ~e3, 7],[4, 3, 2]]) # optional - sage.rings.number_field + sage: S = G.subgroup([m]); S # optional - sage.rings.number_field Subgroup with 1 generators ( [ zeta3 1 0] [ 0 -zeta3 - 1 7] @@ -210,31 +215,14 @@ def subgroup(self, generators, check=True): TESTS:: - sage: TestSuite(G).run() - sage: TestSuite(S).run() - - sage: W = CoxeterGroup(['I',7]) - sage: s = W.simple_reflections() - sage: G = W.subgroup([s[1]]) - sage: G.category() - Category of finite groups - - sage: W = WeylGroup(['A',2]) - sage: s = W.simple_reflections() - sage: G = W.subgroup([s[1]]) - sage: G.category() - Category of finite groups + sage: TestSuite(G).run() # optional - sage.rings.number_field + sage: TestSuite(S).run() # optional - sage.rings.number_field """ try: test = self.is_finite() except NotImplementedError: test = self in Groups().Finite() cat = Groups().Finite() if test else Groups() - # this method enlarges the method with same name of - # ParentLibGAP to cases where the ambient group is not - # inherited from ParentLibGAP. - if isinstance(self, ParentLibGAP): - return ParentLibGAP.subgroup(self, generators) for g in generators: if g not in self: @@ -256,8 +244,8 @@ def ambient(self): EXAMPLES:: - sage: G = GL(2,QQ) - sage: m = matrix(QQ, 2,2, [[3, 0],[~5,1]]) + sage: G = GL(2, QQ) + sage: m = matrix(QQ, 2, 2, [[3, 0], [~5,1]]) sage: S = G.subgroup([m]) sage: S.ambient() is G True @@ -277,10 +265,10 @@ def _repr_(self): EXAMPLES:: - sage: F = GF(5); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,2],[-1,1]]),MS([[1,1],[0,1]])] - sage: G = MatrixGroup(gens) - sage: G + sage: F = GF(5); MS = MatrixSpace(F, 2, 2) # optional - sage.rings.finite_rings + sage: gens = [MS([[1,2], [-1,1]]), MS([[1,1], [0,1]])] # optional - sage.rings.finite_rings + sage: G = MatrixGroup(gens) # optional - sage.rings.finite_rings + sage: G # optional - sage.rings.finite_rings Matrix group over Finite Field of size 5 with 2 generators ( [1 2] [1 1] [4 1], [0 1] @@ -288,11 +276,11 @@ def _repr_(self): case of being a subgroup:: - sage: CF3 = CyclotomicField(3) - sage: G = GL(2, CF3) - sage: e3 = CF3.gen() - sage: m = matrix(CF3, 2,2, [[e3, 1], [0, ~e3]]) - sage: S = G.subgroup([m]); S + sage: CF3 = CyclotomicField(3) # optional - sage.rings.number_field + sage: G = GL(2, CF3) # optional - sage.rings.number_field + sage: e3 = CF3.gen() # optional - sage.rings.number_field + sage: m = matrix(CF3, 2, 2, [[e3, 1], [0, ~e3]]) # optional - sage.rings.number_field + sage: S = G.subgroup([m]); S # optional - sage.rings.number_field Subgroup with 1 generators ( [ zeta3 1] [ 0 -zeta3 - 1] @@ -325,8 +313,8 @@ def _repr_option(self, key): EXAMPLES:: - sage: SO3 = groups.matrix.SO(3, QQ) - sage: SO3._repr_option('element_ascii_art') + sage: SO3 = groups.matrix.SO(3, QQ) # optional - sage.groups + sage: SO3._repr_option('element_ascii_art') # optional - sage.groups True """ if key == 'element_ascii_art': @@ -337,9 +325,9 @@ def _latex_(self): r""" EXAMPLES:: - sage: MS = MatrixSpace(GF(5), 2, 2) - sage: G = MatrixGroup(MS([[1,2],[-1,1]]),MS([[1,1],[0,1]])) - sage: latex(G) + sage: MS = MatrixSpace(GF(5), 2, 2) # optional - sage.rings.finite_rings + sage: G = MatrixGroup(MS([[1,2], [-1,1]]), MS([[1,1], [0,1]])) # optional - sage.rings.finite_rings + sage: latex(G) # optional - sage.rings.finite_rings \left\langle \left(\begin{array}{rr} 1 & 2 \\ 4 & 1 @@ -355,7 +343,9 @@ def sign_representation(self, base_ring=None, side="twosided"): r""" Return the sign representation of ``self`` over ``base_ring``. - WARNING: assumes ``self`` is a matrix group over a field which has embedding over real numbers. + .. WARNING:: + + Assumes ``self`` is a matrix group over a field which has embedding over real numbers. INPUT: @@ -365,19 +355,19 @@ def sign_representation(self, base_ring=None, side="twosided"): EXAMPLES:: sage: G = GL(2, QQ) - sage: V = G.sign_representation() - sage: e = G.an_element() - sage: e + sage: V = G.sign_representation() # optional - sage.combinat + sage: e = G.an_element() # optional - sage.combinat + sage: e # optional - sage.combinat [1 0] [0 1] - sage: V._default_sign(e) + sage: V._default_sign(e) # optional - sage.combinat 1 - sage: m2 = V.an_element() - sage: m2 + sage: m2 = V.an_element() # optional - sage.combinat + sage: m2 # optional - sage.combinat 2*B['v'] - sage: m2*e + sage: m2*e # optional - sage.combinat 2*B['v'] - sage: m2*e*e + sage: m2*e*e # optional - sage.combinat 2*B['v'] """ if base_ring is None: @@ -442,7 +432,7 @@ def degree(self): EXAMPLES:: - sage: SU(5,5).degree() + sage: SU(5,5).degree() # optional - sage.rings.finite_rings 5 """ return self._deg @@ -457,11 +447,11 @@ def matrix_space(self): EXAMPLES:: - sage: F = GF(5); MS = MatrixSpace(F,2,2) - sage: G = MatrixGroup([MS(1), MS([1,2,3,4])]) - sage: G.matrix_space() + sage: F = GF(5); MS = MatrixSpace(F, 2, 2) # optional - sage.rings.finite_rings + sage: G = MatrixGroup([MS(1), MS([1,2,3,4])]) # optional - sage.rings.finite_rings + sage: G.matrix_space() # optional - sage.rings.finite_rings Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 5 - sage: G.matrix_space() is MS + sage: G.matrix_space() is MS # optional - sage.rings.finite_rings True """ return MatrixSpace(self.base_ring(), self.degree()) @@ -486,11 +476,11 @@ def __richcmp__(self, other, op): EXAMPLES:: - sage: G = GL(2,3) - sage: H = MatrixGroup(G.gens()) - sage: H == G + sage: G = GL(2,3) # optional - sage.rings.finite_rings + sage: H = MatrixGroup(G.gens()) # optional - sage.rings.finite_rings + sage: H == G # optional - sage.rings.finite_rings True - sage: G == H + sage: G == H # optional - sage.rings.finite_rings True sage: MS = MatrixSpace(QQ, 2, 2) @@ -502,11 +492,11 @@ def __richcmp__(self, other, op): TESTS:: - sage: G = groups.matrix.GL(4,2) - sage: H = MatrixGroup(G.gens()) - sage: G == H + sage: G = groups.matrix.GL(4,2) # optional - sage.groups sage.rings.finite_rings + sage: H = MatrixGroup(G.gens()) # optional - sage.groups sage.rings.finite_rings + sage: G == H # optional - sage.groups sage.rings.finite_rings True - sage: G != H + sage: G != H # optional - sage.groups sage.rings.finite_rings False """ if not is_MatrixGroup(other): @@ -548,238 +538,3 @@ def __richcmp__(self, other, op): if lx != rx: return richcmp_not_equal(lx, rx, op) return rich_to_bool(op, 0) - -################################################################### -# -# Matrix group over a ring that GAP understands -# -################################################################### - - -class MatrixGroup_gap(GroupMixinLibGAP, MatrixGroup_generic, ParentLibGAP): - - Element = MatrixGroupElement_gap - - def __init__(self, degree, base_ring, libgap_group, ambient=None, category=None): - """ - Base class for matrix groups that implements GAP interface. - - INPUT: - - - ``degree`` -- integer. The degree (matrix size) of the - matrix group. - - - ``base_ring`` -- ring. The base ring of the matrices. - - - ``libgap_group`` -- the defining libgap group. - - - ``ambient`` -- A derived class of :class:`ParentLibGAP` or - ``None`` (default). The ambient class if ``libgap_group`` - has been defined as a subgroup. - - TESTS: - - :: - - sage: from sage.groups.matrix_gps.matrix_group import MatrixGroup_gap - sage: MatrixGroup_gap(2, ZZ, libgap.eval('GL(2, Integers)')) - Matrix group over Integer Ring with 3 generators ( - [0 1] [-1 0] [1 1] - [1 0], [ 0 1], [0 1] - ) - - Check that the slowness of GAP iterators and enumerators for matrix groups - (cf. http://tracker.gap-system.org/issues/369) has been fixed:: - - sage: i = iter(GL(6,5)) - sage: [ next(i) for j in range(8) ] - [ - [1 0 0 0 0 0] [4 0 0 0 0 1] [0 4 0 0 0 0] [0 4 0 0 0 0] - [0 1 0 0 0 0] [4 0 0 0 0 0] [0 0 4 0 0 0] [0 0 4 0 0 0] - [0 0 1 0 0 0] [0 4 0 0 0 0] [0 0 0 4 0 0] [0 0 0 4 0 0] - [0 0 0 1 0 0] [0 0 4 0 0 0] [0 0 0 0 4 0] [0 0 0 0 4 0] - [0 0 0 0 1 0] [0 0 0 4 0 0] [0 0 0 0 0 4] [0 0 0 0 0 4] - [0 0 0 0 0 1], [0 0 0 0 4 0], [1 4 0 0 0 0], [2 4 0 0 0 0], - [3 0 0 0 0 1] [4 0 0 1 3 3] [0 0 0 2 0 0] [1 0 0 0 4 4] - [3 0 0 0 0 0] [4 0 0 0 3 3] [0 0 0 0 4 0] [1 0 0 0 0 4] - [0 4 0 0 0 0] [3 0 0 0 0 1] [2 2 0 0 0 2] [1 0 0 0 0 0] - [0 0 4 0 0 0] [3 0 0 0 0 0] [1 4 0 0 0 0] [0 1 0 0 0 0] - [0 0 0 4 0 0] [0 4 0 0 0 0] [0 2 4 0 0 0] [0 0 1 0 0 0] - [4 0 0 0 2 3], [2 0 3 4 4 4], [0 0 1 4 0 0], [0 0 0 1 0 0] - ] - - And the same for listing the group elements, as well as few other issues:: - - sage: F = GF(3) - sage: gens = [matrix(F,2, [1,0, -1,1]), matrix(F, 2, [1,1,0,1])] - sage: G = MatrixGroup(gens) - sage: G.cardinality() - 24 - sage: v = G.list() - sage: len(v) - 24 - sage: v[:5] - ( - [1 0] [2 0] [0 1] [0 2] [1 2] - [0 1], [0 2], [2 0], [1 0], [2 2] - ) - sage: all(g in G for g in G.list()) - True - - An example over a ring (see :trac:`5241`):: - - sage: M1 = matrix(ZZ,2,[[-1,0],[0,1]]) - sage: M2 = matrix(ZZ,2,[[1,0],[0,-1]]) - sage: M3 = matrix(ZZ,2,[[-1,0],[0,-1]]) - sage: MG = MatrixGroup([M1, M2, M3]) - sage: MG.list() - ( - [1 0] [ 1 0] [-1 0] [-1 0] - [0 1], [ 0 -1], [ 0 1], [ 0 -1] - ) - sage: MG.list()[1] - [ 1 0] - [ 0 -1] - sage: MG.list()[1].parent() - Matrix group over Integer Ring with 3 generators ( - [-1 0] [ 1 0] [-1 0] - [ 0 1], [ 0 -1], [ 0 -1] - ) - - An example over a field (see :trac:`10515`):: - - sage: gens = [matrix(QQ,2,[1,0,0,1])] - sage: MatrixGroup(gens).list() - ( - [1 0] - [0 1] - ) - - Another example over a ring (see :trac:`9437`):: - - sage: len(SL(2, Zmod(4)).list()) - 48 - - An error is raised if the group is not finite:: - - sage: GL(2,ZZ).list() - Traceback (most recent call last): - ... - NotImplementedError: group must be finite - - """ - ParentLibGAP.__init__(self, libgap_group, ambient=ambient) - MatrixGroup_generic.__init__(self, degree, base_ring, category=category) - - def __iter__(self): - """ - Iterate over the elements of the group. - - This method overrides the matrix group enumerator in GAP which - does not (and often just cannot) work for infinite groups. - - TESTS: - - infinite groups can be dealt with:: - - sage: import itertools - sage: W = WeylGroup(["A",3,1]) - sage: list(itertools.islice(W, int(4))) - [ - [1 0 0 0] [-1 1 0 1] [ 1 0 0 0] [ 1 0 0 0] - [0 1 0 0] [ 0 1 0 0] [ 1 -1 1 0] [ 0 1 0 0] - [0 0 1 0] [ 0 0 1 0] [ 0 0 1 0] [ 0 1 -1 1] - [0 0 0 1], [ 0 0 0 1], [ 0 0 0 1], [ 0 0 0 1] - ] - - and finite groups, too:: - - sage: G = GL(6,5) - sage: list(itertools.islice(G, int(4))) - [ - [1 0 0 0 0 0] [4 0 0 0 0 1] [0 4 0 0 0 0] [0 4 0 0 0 0] - [0 1 0 0 0 0] [4 0 0 0 0 0] [0 0 4 0 0 0] [0 0 4 0 0 0] - [0 0 1 0 0 0] [0 4 0 0 0 0] [0 0 0 4 0 0] [0 0 0 4 0 0] - [0 0 0 1 0 0] [0 0 4 0 0 0] [0 0 0 0 4 0] [0 0 0 0 4 0] - [0 0 0 0 1 0] [0 0 0 4 0 0] [0 0 0 0 0 4] [0 0 0 0 0 4] - [0 0 0 0 0 1], [0 0 0 0 4 0], [1 4 0 0 0 0], [2 4 0 0 0 0] - ] - """ - if not self.is_finite(): - # use implementation from category framework - for g in super(Group, self).__iter__(): - yield g - return - # Use the standard GAP iterator for finite groups - for g in super().__iter__(): - yield g - return - - def _check_matrix(self, x_sage, x_gap): - """ - Check whether the matrix ``x`` defines a group element. - - This is used by the element constructor (if you pass - ``check=True``, the default) that the defining matrix is valid - for this parent. Derived classes must override this to verify - that the matrix is, for example, orthogonal or symplectic. - - INPUT: - - - ``x_sage`` -- a Sage matrix in the correct matrix space (degree - and base ring). - - - ``x_gap`` -- the corresponding LibGAP matrix. - - OUTPUT: - - A ``TypeError`` must be raised if ``x`` is invalid. - - EXAMPLES:: - - sage: m1 = matrix(GF(11), [(0, -1), (1, 0)]) - sage: m2 = matrix(GF(11), [(0, -1), (1, -1)]) - sage: G = MatrixGroup([m1, m2]) - sage: G([1,2,0,1]) - [1 2] - [0 1] - sage: G([1,1,1,0]) - Traceback (most recent call last): - ... - TypeError: matrix is not in the finitely generated group - """ - from sage.libs.gap.libgap import libgap - libgap_contains = libgap.eval(r'\in') - is_contained = libgap_contains(x_gap, self.gap()) - if not is_contained.sage(): - raise TypeError('matrix is not in the finitely generated group') - - def _subgroup_constructor(self, libgap_subgroup): - """ - Return a finitely generated subgroup. - - See - :meth:`sage.groups.libgap_wrapper.ParentLibGAP._subgroup_constructor` - for details. - - TESTS:: - - sage: SL2Z = SL(2,ZZ) - sage: S, T = SL2Z.gens() - sage: G = SL2Z.subgroup([T^2]); G # indirect doctest - Subgroup with 1 generators ( - [1 2] - [0 1] - ) of Special Linear Group of degree 2 over Integer Ring - sage: G.ambient() is SL2Z - True - """ - cat = Groups() - if self in Groups().Finite(): - cat = cat.Finite() - from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap - return FinitelyGeneratedMatrixGroup_gap(self.degree(), self.base_ring(), - libgap_subgroup, ambient=self, - category=cat) - - from sage.groups.generic import structure_description diff --git a/src/sage/groups/matrix_gps/matrix_group_gap.py b/src/sage/groups/matrix_gps/matrix_group_gap.py new file mode 100644 index 00000000000..b4c06acbd0f --- /dev/null +++ b/src/sage/groups/matrix_gps/matrix_group_gap.py @@ -0,0 +1,311 @@ +r""" +Matrix group over a ring that GAP understands +""" + +# **************************************************************************** +# Copyright (C) 2006 William Stein +# 2006-2008 David Joyner +# 2013 Volker Braun +# 2017 Dima Pasechnik +# 2021 Frédéric Chapoton +# 2018-2019 Sebastian Oehms +# 2020 Vincent Delecroix +# 2023 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.categories.groups import Groups +from sage.groups.group import Group +from sage.groups.libgap_mixin import GroupMixinLibGAP +from sage.groups.libgap_wrapper import ParentLibGAP +from sage.groups.matrix_gps.group_element import MatrixGroupElement_gap +from sage.groups.matrix_gps.matrix_group import MatrixGroup_generic + + +class MatrixGroup_gap(GroupMixinLibGAP, MatrixGroup_generic, ParentLibGAP): + + Element = MatrixGroupElement_gap + + def __init__(self, degree, base_ring, libgap_group, ambient=None, category=None): + """ + Base class for matrix groups that implements GAP interface. + + INPUT: + + - ``degree`` -- integer. The degree (matrix size) of the + matrix group. + + - ``base_ring`` -- ring. The base ring of the matrices. + + - ``libgap_group`` -- the defining libgap group. + + - ``ambient`` -- A derived class of :class:`ParentLibGAP` or + ``None`` (default). The ambient class if ``libgap_group`` + has been defined as a subgroup. + + TESTS: + + :: + + sage: from sage.groups.matrix_gps.matrix_group_gap import MatrixGroup_gap + sage: MatrixGroup_gap(2, ZZ, libgap.eval('GL(2, Integers)')) + Matrix group over Integer Ring with 3 generators ( + [0 1] [-1 0] [1 1] + [1 0], [ 0 1], [0 1] + ) + + Check that the slowness of GAP iterators and enumerators for matrix groups + (cf. http://tracker.gap-system.org/issues/369) has been fixed:: + + sage: i = iter(GL(6,5)) + sage: [ next(i) for j in range(8) ] + [ + [1 0 0 0 0 0] [4 0 0 0 0 1] [0 4 0 0 0 0] [0 4 0 0 0 0] + [0 1 0 0 0 0] [4 0 0 0 0 0] [0 0 4 0 0 0] [0 0 4 0 0 0] + [0 0 1 0 0 0] [0 4 0 0 0 0] [0 0 0 4 0 0] [0 0 0 4 0 0] + [0 0 0 1 0 0] [0 0 4 0 0 0] [0 0 0 0 4 0] [0 0 0 0 4 0] + [0 0 0 0 1 0] [0 0 0 4 0 0] [0 0 0 0 0 4] [0 0 0 0 0 4] + [0 0 0 0 0 1], [0 0 0 0 4 0], [1 4 0 0 0 0], [2 4 0 0 0 0], + [3 0 0 0 0 1] [4 0 0 1 3 3] [0 0 0 2 0 0] [1 0 0 0 4 4] + [3 0 0 0 0 0] [4 0 0 0 3 3] [0 0 0 0 4 0] [1 0 0 0 0 4] + [0 4 0 0 0 0] [3 0 0 0 0 1] [2 2 0 0 0 2] [1 0 0 0 0 0] + [0 0 4 0 0 0] [3 0 0 0 0 0] [1 4 0 0 0 0] [0 1 0 0 0 0] + [0 0 0 4 0 0] [0 4 0 0 0 0] [0 2 4 0 0 0] [0 0 1 0 0 0] + [4 0 0 0 2 3], [2 0 3 4 4 4], [0 0 1 4 0 0], [0 0 0 1 0 0] + ] + + And the same for listing the group elements, as well as few other issues:: + + sage: F = GF(3) + sage: gens = [matrix(F,2, [1,0, -1,1]), matrix(F, 2, [1,1,0,1])] + sage: G = MatrixGroup(gens) + sage: G.cardinality() + 24 + sage: v = G.list() + sage: len(v) + 24 + sage: v[:5] + ( + [1 0] [2 0] [0 1] [0 2] [1 2] + [0 1], [0 2], [2 0], [1 0], [2 2] + ) + sage: all(g in G for g in G.list()) + True + + An example over a ring (see :trac:`5241`):: + + sage: M1 = matrix(ZZ,2,[[-1,0],[0,1]]) + sage: M2 = matrix(ZZ,2,[[1,0],[0,-1]]) + sage: M3 = matrix(ZZ,2,[[-1,0],[0,-1]]) + sage: MG = MatrixGroup([M1, M2, M3]) + sage: MG.list() + ( + [1 0] [ 1 0] [-1 0] [-1 0] + [0 1], [ 0 -1], [ 0 1], [ 0 -1] + ) + sage: MG.list()[1] + [ 1 0] + [ 0 -1] + sage: MG.list()[1].parent() + Matrix group over Integer Ring with 3 generators ( + [-1 0] [ 1 0] [-1 0] + [ 0 1], [ 0 -1], [ 0 -1] + ) + + An example over a field (see :trac:`10515`):: + + sage: gens = [matrix(QQ,2,[1,0,0,1])] + sage: MatrixGroup(gens).list() + ( + [1 0] + [0 1] + ) + + Another example over a ring (see :trac:`9437`):: + + sage: len(SL(2, Zmod(4)).list()) + 48 + + An error is raised if the group is not finite:: + + sage: GL(2,ZZ).list() + Traceback (most recent call last): + ... + NotImplementedError: group must be finite + + """ + ParentLibGAP.__init__(self, libgap_group, ambient=ambient) + MatrixGroup_generic.__init__(self, degree, base_ring, category=category) + + def __iter__(self): + """ + Iterate over the elements of the group. + + This method overrides the matrix group enumerator in GAP which + does not (and often just cannot) work for infinite groups. + + TESTS: + + infinite groups can be dealt with:: + + sage: import itertools + sage: W = WeylGroup(["A",3,1]) + sage: list(itertools.islice(W, int(4))) + [ + [1 0 0 0] [-1 1 0 1] [ 1 0 0 0] [ 1 0 0 0] + [0 1 0 0] [ 0 1 0 0] [ 1 -1 1 0] [ 0 1 0 0] + [0 0 1 0] [ 0 0 1 0] [ 0 0 1 0] [ 0 1 -1 1] + [0 0 0 1], [ 0 0 0 1], [ 0 0 0 1], [ 0 0 0 1] + ] + + and finite groups, too:: + + sage: G = GL(6,5) + sage: list(itertools.islice(G, int(4))) + [ + [1 0 0 0 0 0] [4 0 0 0 0 1] [0 4 0 0 0 0] [0 4 0 0 0 0] + [0 1 0 0 0 0] [4 0 0 0 0 0] [0 0 4 0 0 0] [0 0 4 0 0 0] + [0 0 1 0 0 0] [0 4 0 0 0 0] [0 0 0 4 0 0] [0 0 0 4 0 0] + [0 0 0 1 0 0] [0 0 4 0 0 0] [0 0 0 0 4 0] [0 0 0 0 4 0] + [0 0 0 0 1 0] [0 0 0 4 0 0] [0 0 0 0 0 4] [0 0 0 0 0 4] + [0 0 0 0 0 1], [0 0 0 0 4 0], [1 4 0 0 0 0], [2 4 0 0 0 0] + ] + """ + if not self.is_finite(): + # use implementation from category framework + for g in super(Group, self).__iter__(): + yield g + return + # Use the standard GAP iterator for finite groups + for g in super().__iter__(): + yield g + return + + def _check_matrix(self, x_sage, x_gap): + """ + Check whether the matrix ``x`` defines a group element. + + This is used by the element constructor (if you pass + ``check=True``, the default) that the defining matrix is valid + for this parent. Derived classes must override this to verify + that the matrix is, for example, orthogonal or symplectic. + + INPUT: + + - ``x_sage`` -- a Sage matrix in the correct matrix space (degree + and base ring). + + - ``x_gap`` -- the corresponding LibGAP matrix. + + OUTPUT: + + A ``TypeError`` must be raised if ``x`` is invalid. + + EXAMPLES:: + + sage: m1 = matrix(GF(11), [(0, -1), (1, 0)]) + sage: m2 = matrix(GF(11), [(0, -1), (1, -1)]) + sage: G = MatrixGroup([m1, m2]) + sage: G([1,2,0,1]) + [1 2] + [0 1] + sage: G([1,1,1,0]) + Traceback (most recent call last): + ... + TypeError: matrix is not in the finitely generated group + """ + from sage.libs.gap.libgap import libgap + libgap_contains = libgap.eval(r'\in') + is_contained = libgap_contains(x_gap, self.gap()) + if not is_contained.sage(): + raise TypeError('matrix is not in the finitely generated group') + + def subgroup(self, generators, check=True): + """ + Return the subgroup generated by the given generators. + + INPUT: + + - ``generators`` -- a list/tuple/iterable of group elements of ``self`` + - ``check`` -- boolean (optional, default: ``True``). Whether to check that each matrix is invertible. + + OUTPUT: The subgroup generated by ``generators`` as an instance of :class:`FinitelyGeneratedMatrixGroup_gap` + + EXAMPLES:: + + sage: UCF = UniversalCyclotomicField() + sage: G = GL(3, UCF) + sage: e3 = UCF.gen(3); e5 = UCF.gen(5) + sage: m = matrix(UCF, 3,3, [[e3, 1, 0], [0, e5, 7],[4, 3, 2]]) + sage: S = G.subgroup([m]); S + Subgroup with 1 generators ( + [E(3) 1 0] + [ 0 E(5) 7] + [ 4 3 2] + ) of General Linear Group of degree 3 over Universal Cyclotomic Field + + sage: CF3 = CyclotomicField(3) + sage: G = GL(3, CF3) + sage: e3 = CF3.gen() + sage: m = matrix(CF3, 3,3, [[e3, 1, 0], [0, ~e3, 7],[4, 3, 2]]) + sage: S = G.subgroup([m]); S + Subgroup with 1 generators ( + [ zeta3 1 0] + [ 0 -zeta3 - 1 7] + [ 4 3 2] + ) of General Linear Group of degree 3 over Cyclotomic Field of order 3 and degree 2 + + TESTS:: + + sage: TestSuite(G).run() + sage: TestSuite(S).run() + + sage: W = CoxeterGroup(['I',7]) + sage: s = W.simple_reflections() + sage: G = W.subgroup([s[1]]) + sage: G.category() + Category of finite groups + + sage: W = WeylGroup(['A',2]) + sage: s = W.simple_reflections() + sage: G = W.subgroup([s[1]]) + sage: G.category() + Category of finite groups + """ + # Override MatrixGroup_generic.subgroup + return ParentLibGAP.subgroup(self, generators) + + def _subgroup_constructor(self, libgap_subgroup): + """ + Return a finitely generated subgroup. + + See + :meth:`sage.groups.libgap_wrapper.ParentLibGAP._subgroup_constructor` + for details. + + TESTS:: + + sage: SL2Z = SL(2,ZZ) + sage: S, T = SL2Z.gens() + sage: G = SL2Z.subgroup([T^2]); G # indirect doctest + Subgroup with 1 generators ( + [1 2] + [0 1] + ) of Special Linear Group of degree 2 over Integer Ring + sage: G.ambient() is SL2Z + True + """ + cat = Groups() + if self in Groups().Finite(): + cat = cat.Finite() + from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap + return FinitelyGeneratedMatrixGroup_gap(self.degree(), self.base_ring(), + libgap_subgroup, ambient=self, + category=cat) + + from sage.groups.generic import structure_description diff --git a/src/sage/groups/matrix_gps/named_group.py b/src/sage/groups/matrix_gps/named_group.py index 39bb43c710a..08717dd7110 100644 --- a/src/sage/groups/matrix_gps/named_group.py +++ b/src/sage/groups/matrix_gps/named_group.py @@ -8,17 +8,17 @@ sage: SL(2, ZZ) Special Linear Group of degree 2 over Integer Ring - sage: G = SL(2,GF(3)); G + sage: G = SL(2, GF(3)); G # optional - sage.rings.finite_rings Special Linear Group of degree 2 over Finite Field of size 3 - sage: G.is_finite() + sage: G.is_finite() # optional - sage.rings.finite_rings True - sage: G.conjugacy_classes_representatives() + sage: G.conjugacy_classes_representatives() # optional - sage.rings.finite_rings ( [1 0] [0 2] [0 1] [2 0] [0 2] [0 1] [0 2] [0 1], [1 1], [2 1], [0 2], [1 2], [2 2], [1 0] ) - sage: G = SL(6,GF(5)) - sage: G.gens() + sage: G = SL(6, GF(5)) # optional - sage.rings.finite_rings + sage: G.gens() # optional - sage.rings.finite_rings ( [2 0 0 0 0 0] [4 0 0 0 0 1] [0 3 0 0 0 0] [4 0 0 0 0 0] @@ -31,7 +31,10 @@ ############################################################################## # Copyright (C) 2006 David Joyner and William Stein -# Copyright (C) 2013 Volker Braun +# 2013 Volker Braun +# 2017 Frédéric Chapoton +# 2018 Travis Scrimshaw +# 2018 Sebastian Oehms # # Distributed under the terms of the GNU General Public License (GPL) # @@ -40,9 +43,8 @@ # http://www.gnu.org/licenses/ ############################################################################## +from sage.groups.matrix_gps.matrix_group import MatrixGroup_generic from sage.structure.unique_representation import CachedRepresentation -from sage.groups.matrix_gps.matrix_group import ( - MatrixGroup_generic, MatrixGroup_gap ) def normalize_args_vectorspace(*args, **kwds): @@ -81,12 +83,12 @@ def normalize_args_vectorspace(*args, **kwds): TESTS:: sage: from sage.groups.matrix_gps.named_group import normalize_args_vectorspace - sage: A = AffineSpace(2, GF(4,'a')); A + sage: A = AffineSpace(2, GF(4,'a')); A # optional - sage.rings.finite_rings Affine Space of dimension 2 over Finite Field in a of size 2^2 - sage: normalize_args_vectorspace(A) + sage: normalize_args_vectorspace(A) # optional - sage.rings.finite_rings (2, Finite Field in a of size 2^2) - sage: normalize_args_vectorspace(2,4) # shorthand + sage: normalize_args_vectorspace(2,4) # shorthand # optional - sage.rings.finite_rings (2, Finite Field in a of size 2^2) sage: V = ZZ^3; V @@ -148,12 +150,12 @@ def normalize_args_invariant_form(R, d, invariant_form): TESTS:: sage: from sage.groups.matrix_gps.named_group import normalize_args_invariant_form - sage: CF3 = CyclotomicField(3) - sage: m = normalize_args_invariant_form(CF3, 3, (1,2,3,0,2,0,0,2,1)); m + sage: CF3 = CyclotomicField(3) # optional - sage.rings.number_field + sage: m = normalize_args_invariant_form(CF3, 3, (1,2,3,0,2,0,0,2,1)); m # optional - sage.rings.number_field [1 2 3] [0 2 0] [0 2 1] - sage: m.base_ring() == CF3 + sage: m.base_ring() == CF3 # optional - sage.rings.number_field True sage: normalize_args_invariant_form(ZZ, 3, (1,2,3,0,2,0,0,2)) @@ -286,54 +288,15 @@ def __richcmp__(self, other, op): EXAMPLES:: - sage: G = GL(2,3) - sage: G == MatrixGroup(G.gens()) + sage: G = GL(2,3) # optional - sage.rings.finite_rings + sage: G == MatrixGroup(G.gens()) # optional - sage.rings.finite_rings True - sage: G = groups.matrix.GL(4,2) - sage: H = MatrixGroup(G.gens()) - sage: G == H + sage: G = groups.matrix.GL(4,2) # optional - sage.rings.finite_rings + sage: H = MatrixGroup(G.gens()) # optional - sage.rings.finite_rings + sage: G == H # optional - sage.rings.finite_rings True - sage: G != H + sage: G != H # optional - sage.rings.finite_rings False """ return MatrixGroup_generic.__richcmp__(self, other, op) - - -class NamedMatrixGroup_gap(NamedMatrixGroup_generic, MatrixGroup_gap): - - def __init__(self, degree, base_ring, special, sage_name, latex_string, - gap_command_string, category=None): - """ - Base class for "named" matrix groups using LibGAP - - INPUT: - - - ``degree`` -- integer. The degree (number of rows/columns of - matrices). - - - ``base_ring`` -- ring. The base ring of the matrices. - - - ``special`` -- boolean. Whether the matrix group is special, - that is, elements have determinant one. - - - ``latex_string`` -- string. The latex representation. - - - ``gap_command_string`` -- string. The GAP command to construct - the matrix group. - - EXAMPLES:: - - sage: G = GL(2, GF(3)) - sage: from sage.groups.matrix_gps.named_group import NamedMatrixGroup_gap - sage: isinstance(G, NamedMatrixGroup_gap) - True - """ - from sage.libs.gap.libgap import libgap - group = libgap.eval(gap_command_string) - MatrixGroup_gap.__init__(self, degree, base_ring, group, - category=category) - self._special = special - self._gap_string = gap_command_string - self._name_string = sage_name - self._latex_string = latex_string diff --git a/src/sage/groups/matrix_gps/named_group_gap.py b/src/sage/groups/matrix_gps/named_group_gap.py new file mode 100644 index 00000000000..b8b1f54722c --- /dev/null +++ b/src/sage/groups/matrix_gps/named_group_gap.py @@ -0,0 +1,57 @@ +""" +Base for Classical Matrix Groups with GAP +""" + +############################################################################## +# Copyright (C) 2006 David Joyner and William Stein +# 2013 Volker Braun +# 2017 John Palmieri +# 2023 Matthias Koeppe +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +############################################################################## + +from sage.groups.matrix_gps.matrix_group_gap import MatrixGroup_gap +from sage.groups.matrix_gps.named_group import NamedMatrixGroup_generic + +class NamedMatrixGroup_gap(NamedMatrixGroup_generic, MatrixGroup_gap): + + def __init__(self, degree, base_ring, special, sage_name, latex_string, + gap_command_string, category=None): + """ + Base class for "named" matrix groups using LibGAP + + INPUT: + + - ``degree`` -- integer. The degree (number of rows/columns of + matrices). + + - ``base_ring`` -- ring. The base ring of the matrices. + + - ``special`` -- boolean. Whether the matrix group is special, + that is, elements have determinant one. + + - ``latex_string`` -- string. The latex representation. + + - ``gap_command_string`` -- string. The GAP command to construct + the matrix group. + + EXAMPLES:: + + sage: G = GL(2, GF(3)) + sage: from sage.groups.matrix_gps.named_group_gap import NamedMatrixGroup_gap + sage: isinstance(G, NamedMatrixGroup_gap) + True + """ + from sage.libs.gap.libgap import libgap + group = libgap.eval(gap_command_string) + MatrixGroup_gap.__init__(self, degree, base_ring, group, + category=category) + self._special = special + self._gap_string = gap_command_string + self._name_string = sage_name + self._latex_string = latex_string diff --git a/src/sage/groups/matrix_gps/orthogonal.py b/src/sage/groups/matrix_gps/orthogonal.py index 6b71f62d7ed..694401d6d31 100644 --- a/src/sage/groups/matrix_gps/orthogonal.py +++ b/src/sage/groups/matrix_gps/orthogonal.py @@ -33,12 +33,13 @@ EXAMPLES:: - sage: GO(3,7) + sage: GO(3,7) # optional - sage.rings.finite_rings General Orthogonal Group of degree 3 over Finite Field of size 7 - sage: G = SO( 4, GF(7), 1); G - Special Orthogonal Group of degree 4 and form parameter 1 over Finite Field of size 7 - sage: G.random_element() # random + sage: G = SO(4, GF(7), 1); G # optional - sage.rings.finite_rings + Special Orthogonal Group of degree 4 and form parameter 1 + over Finite Field of size 7 + sage: G.random_element() # random # optional - sage.rings.finite_rings [4 3 5 2] [6 6 4 0] [0 4 6 0] @@ -46,14 +47,14 @@ TESTS:: - sage: G = GO(3, GF(5)) - sage: latex(G) + sage: G = GO(3, GF(5)) # optional - sage.rings.finite_rings + sage: latex(G) # optional - sage.rings.finite_rings \text{GO}_{3}(\Bold{F}_{5}) - sage: G = SO(3, GF(5)) - sage: latex(G) + sage: G = SO(3, GF(5)) # optional - sage.rings.finite_rings + sage: latex(G) # optional - sage.rings.finite_rings \text{SO}_{3}(\Bold{F}_{5}) - sage: G = SO(4, GF(5), 1) - sage: latex(G) + sage: G = SO(4, GF(5), 1) # optional - sage.rings.finite_rings + sage: latex(G) # optional - sage.rings.finite_rings \text{SO}_{4}(\Bold{F}_{5}, +) AUTHORS: @@ -75,7 +76,11 @@ # **************************************************************************** # Copyright (C) 2006 David Joyner and William Stein -# Copyright (C) 2013 Volker Braun +# 2009 Mike Hansen +# 2013 Volker Braun +# 2018 Sebastian Oehms +# 2018 Travis Scrimshaw +# 2023 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -90,8 +95,8 @@ from sage.misc.cachefunc import cached_method from sage.groups.matrix_gps.named_group import ( normalize_args_vectorspace, normalize_args_invariant_form, - NamedMatrixGroup_generic, NamedMatrixGroup_gap) -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap + NamedMatrixGroup_generic) + def normalize_args_e(degree, ring, e): """ @@ -116,13 +121,13 @@ def normalize_args_e(degree, ring, e): TESTS:: sage: from sage.groups.matrix_gps.orthogonal import normalize_args_e - sage: normalize_args_e(2, GF(3), +1) + sage: normalize_args_e(2, GF(3), +1) # optional - sage.rings.finite_rings 1 - sage: normalize_args_e(3, GF(3), 0) + sage: normalize_args_e(3, GF(3), 0) # optional - sage.rings.finite_rings 0 - sage: normalize_args_e(3, GF(3), +1) + sage: normalize_args_e(3, GF(3), +1) # optional - sage.rings.finite_rings 0 - sage: normalize_args_e(2, GF(3), 0) + sage: normalize_args_e(2, GF(3), 0) # optional - sage.rings.finite_rings Traceback (most recent call last): ... ValueError: must have e=-1 or e=1 for even degree @@ -149,14 +154,14 @@ def _OG(n, R, special, e=0, var='a', invariant_form=None): Check that :trac:`26028` is fixed:: - sage: GO(3,25).order() # indirect doctest + sage: GO(3,25).order() # indirect doctest # optional - sage.rings.finite_rings 31200 Check that :trac:`28054` is fixed:: - sage: G = SO(2, GF(3), -1) - sage: m = G.invariant_form() - sage: G2 = SO(2, GF(3), 1, invariant_form=m) + sage: G = SO(2, GF(3), -1) # optional - sage.rings.finite_rings + sage: m = G.invariant_form() # optional - sage.rings.finite_rings + sage: G2 = SO(2, GF(3), 1, invariant_form=m) # optional - sage.rings.finite_rings Traceback (most recent call last): ... NotImplementedError: invariant_form for finite groups is fixed by GAP @@ -203,10 +208,15 @@ def _OG(n, R, special, e=0, var='a', invariant_form=None): '+' if e == 1 else '-') if isinstance(ring, FiniteField): - cmd = '{0}O({1}, {2}, {3})'.format(ltx_prefix, e, degree, ring.order()) - return OrthogonalMatrixGroup_gap(degree, ring, False, name, ltx, cmd) - else: - return OrthogonalMatrixGroup_generic(degree, ring, False, name, ltx, invariant_form=invariant_form) + try: + from .orthogonal_gap import OrthogonalMatrixGroup_gap + except ImportError: + pass + else: + cmd = '{0}O({1}, {2}, {3})'.format(ltx_prefix, e, degree, ring.order()) + return OrthogonalMatrixGroup_gap(degree, ring, False, name, ltx, cmd) + + return OrthogonalMatrixGroup_generic(degree, ring, False, name, ltx, invariant_form=invariant_form) ######################################################################## @@ -258,11 +268,11 @@ def GO(n, R, e=0, var='a', invariant_form=None): EXAMPLES:: - sage: GO( 3, GF(7)) + sage: GO(3, GF(7)) # optional - sage.rings.finite_rings General Orthogonal Group of degree 3 over Finite Field of size 7 - sage: GO( 3, GF(7)).order() + sage: GO(3, GF(7)).order() # optional - sage.rings.finite_rings 672 - sage: GO( 3, GF(7)).gens() + sage: GO(3, GF(7)).gens() # optional - sage.rings.finite_rings ( [3 0 0] [0 1 0] [0 5 0] [1 6 6] @@ -271,9 +281,9 @@ def GO(n, R, e=0, var='a', invariant_form=None): Using the ``invariant_form`` option:: - sage: m = matrix(QQ, 3,3, [[0, 1, 0], [1, 0, 0], [0, 0, 3]]) - sage: GO3 = GO(3,QQ) - sage: GO3m = GO(3,QQ, invariant_form=m) + sage: m = matrix(QQ, 3, 3, [[0, 1, 0], [1, 0, 0], [0, 0, 3]]) + sage: GO3 = GO(3, QQ) + sage: GO3m = GO(3, QQ, invariant_form=m) sage: GO3 == GO3m False sage: GO3.invariant_form() @@ -284,13 +294,13 @@ def GO(n, R, e=0, var='a', invariant_form=None): [0 1 0] [1 0 0] [0 0 3] - sage: pm = Permutation([2,3,1]).to_matrix() - sage: g = GO3(pm); g in GO3; g + sage: pm = Permutation([2,3,1]).to_matrix() # optional - sage.combinat + sage: g = GO3(pm); g in GO3; g # optional - sage.combinat True [0 0 1] [1 0 0] [0 1 0] - sage: GO3m(pm) + sage: GO3m(pm) # optional - sage.combinat Traceback (most recent call last): ... TypeError: matrix must be orthogonal with respect to the symmetric form @@ -298,15 +308,16 @@ def GO(n, R, e=0, var='a', invariant_form=None): [1 0 0] [0 0 3] - sage: GO(3,3, invariant_form=[[1,0,0],[0,2,0],[0,0,1]]) + sage: GO(3,3, invariant_form=[[1,0,0], [0,2,0], [0,0,1]]) Traceback (most recent call last): ... NotImplementedError: invariant_form for finite groups is fixed by GAP - sage: 5+5 + sage: 5 + 5 10 sage: R. = ZZ[] - sage: GO(2, R, invariant_form=[[x,0],[0,1]]) - General Orthogonal Group of degree 2 over Univariate Polynomial Ring in x over Integer Ring with respect to symmetric form + sage: GO(2, R, invariant_form=[[x,0], [0,1]]) + General Orthogonal Group of degree 2 over + Univariate Polynomial Ring in x over Integer Ring with respect to symmetric form [x 0] [0 1] @@ -364,19 +375,19 @@ def SO(n, R, e=None, var='a', invariant_form=None): EXAMPLES:: - sage: G = SO(3,GF(5)) - sage: G + sage: G = SO(3,GF(5)) # optional - sage.rings.finite_rings + sage: G # optional - sage.rings.finite_rings Special Orthogonal Group of degree 3 over Finite Field of size 5 - sage: G = SO(3,GF(5)) - sage: G.gens() + sage: G = SO(3,GF(5)) # optional - sage.rings.finite_rings + sage: G.gens() # optional - sage.rings.finite_rings ( [2 0 0] [3 2 3] [1 4 4] [0 3 0] [0 2 0] [4 0 0] [0 0 1], [0 3 1], [2 0 4] ) - sage: G = SO(3,GF(5)) - sage: G.as_matrix_group() + sage: G = SO(3,GF(5)) # optional - sage.rings.finite_rings + sage: G.as_matrix_group() # optional - sage.rings.finite_rings Matrix group over Finite Field of size 5 with 3 generators ( [2 0 0] [3 2 3] [1 4 4] [0 3 0] [0 2 0] [4 0 0] @@ -385,27 +396,27 @@ def SO(n, R, e=None, var='a', invariant_form=None): Using the ``invariant_form`` option:: - sage: CF3 = CyclotomicField(3); e3 = CF3.gen() - sage: m = matrix(CF3, 3,3, [[1,e3,0],[e3,2,0],[0,0,1]]) - sage: SO3 = SO(3, CF3) - sage: SO3m = SO(3, CF3, invariant_form=m) - sage: SO3 == SO3m + sage: CF3 = CyclotomicField(3); e3 = CF3.gen() # optional - sage.rings.number_field + sage: m = matrix(CF3, 3, 3, [[1,e3,0], [e3,2,0], [0,0,1]]) # optional - sage.rings.number_field + sage: SO3 = SO(3, CF3) # optional - sage.rings.number_field + sage: SO3m = SO(3, CF3, invariant_form=m) # optional - sage.rings.number_field + sage: SO3 == SO3m # optional - sage.rings.number_field False - sage: SO3.invariant_form() + sage: SO3.invariant_form() # optional - sage.rings.number_field [1 0 0] [0 1 0] [0 0 1] - sage: SO3m.invariant_form() + sage: SO3m.invariant_form() # optional - sage.rings.number_field [ 1 zeta3 0] [zeta3 2 0] [ 0 0 1] - sage: pm = Permutation([2,3,1]).to_matrix() - sage: g = SO3(pm); g in SO3; g + sage: pm = Permutation([2,3,1]).to_matrix() # optional - sage.combinat + sage: g = SO3(pm); g in SO3; g # optional - sage.combinat sage.rings.number_field True [0 0 1] [1 0 0] [0 1 0] - sage: SO3m(pm) + sage: SO3m(pm) # optional - sage.combinat sage.rings.number_field Traceback (most recent call last): ... TypeError: matrix must be orthogonal with respect to the symmetric form @@ -413,7 +424,7 @@ def SO(n, R, e=None, var='a', invariant_form=None): [zeta3 2 0] [ 0 0 1] - sage: SO(3,5, invariant_form=[[1,0,0],[0,2,0],[0,0,3]]) + sage: SO(3, 5, invariant_form=[[1,0,0], [0,2,0], [0,0,3]]) # optional - sage.combinat sage.rings.number_field Traceback (most recent call last): ... NotImplementedError: invariant_form for finite groups is fixed by GAP @@ -422,8 +433,8 @@ def SO(n, R, e=None, var='a', invariant_form=None): TESTS:: - sage: TestSuite(SO3m).run() - sage: groups.matrix.SO(2, 3, e=1) + sage: TestSuite(SO3m).run() # optional - sage.rings.number_field + sage: groups.matrix.SO(2, 3, e=1) # optional - sage.rings.number_field Special Orthogonal Group of degree 2 and form parameter 1 over Finite Field of size 3 """ return _OG(n, R, True, e=e, var=var, invariant_form=invariant_form) @@ -439,20 +450,20 @@ class OrthogonalMatrixGroup_generic(NamedMatrixGroup_generic): EXAMPLES:: - sage: G = GO(3, GF(7)); G + sage: G = GO(3, GF(7)); G # optional - sage.rings.finite_rings General Orthogonal Group of degree 3 over Finite Field of size 7 - sage: latex(G) + sage: latex(G) # optional - sage.rings.finite_rings \text{GO}_{3}(\Bold{F}_{7}) - sage: G = SO(3, GF(5)); G + sage: G = SO(3, GF(5)); G # optional - sage.rings.finite_rings Special Orthogonal Group of degree 3 over Finite Field of size 5 - sage: latex(G) + sage: latex(G) # optional - sage.rings.finite_rings \text{SO}_{3}(\Bold{F}_{5}) - sage: CF3 = CyclotomicField(3); e3 = CF3.gen() - sage: m = matrix(CF3, 3,3, [[1,e3,0],[e3,2,0],[0,0,1]]) - sage: G = SO(3, CF3, invariant_form=m) - sage: latex(G) + sage: CF3 = CyclotomicField(3); e3 = CF3.gen() # optional - sage.rings.number_field + sage: m = matrix(CF3, 3,3, [[1,e3,0],[e3,2,0],[0,0,1]]) # optional - sage.rings.number_field + sage: G = SO(3, CF3, invariant_form=m) # optional - sage.rings.number_field + sage: latex(G) # optional - sage.rings.number_field \text{SO}_{3}(\Bold{Q}(\zeta_{3}))\text{ with respect to non positive definite symmetric form }\left(\begin{array}{rrr} 1 & \zeta_{3} & 0 \\ \zeta_{3} & 2 & 0 \\ @@ -471,10 +482,10 @@ def invariant_bilinear_form(self): EXAMPLES:: - sage: GO(2,3,+1).invariant_bilinear_form() + sage: GO(2,3,+1).invariant_bilinear_form() # optional - sage.rings.finite_rings [0 1] [1 0] - sage: GO(2,3,-1).invariant_bilinear_form() + sage: GO(2,3,-1).invariant_bilinear_form() # optional - sage.rings.finite_rings [2 1] [1 1] sage: G = GO(4, QQ) @@ -483,7 +494,7 @@ def invariant_bilinear_form(self): [0 1 0 0] [0 0 1 0] [0 0 0 1] - sage: GO3m = GO(3,QQ, invariant_form=(1,0,0,0,2,0,0,0,3)) + sage: GO3m = GO(3, QQ, invariant_form=(1,0,0, 0,2,0, 0,0,3)) sage: GO3m.invariant_bilinear_form() [1 0 0] [0 2 0] @@ -516,8 +527,8 @@ def _check_matrix(self, x, *args): EXAMPLES:: - sage: G = GO(4, GF(5), +1) - sage: G._check_matrix(G.an_element().matrix()) + sage: G = GO(4, GF(5), +1) # optional - sage.rings.finite_rings + sage: G._check_matrix(G.an_element().matrix()) # optional - sage.rings.finite_rings """ if self._special and x.determinant() != 1: raise TypeError('matrix must have determinant one') @@ -528,120 +539,3 @@ def _check_matrix(self, x, *args): else: raise TypeError('matrix must be orthogonal with respect to the symmetric form\n%s' %(F)) # TODO: check that quadratic form is preserved in characteristic two - -class OrthogonalMatrixGroup_gap(OrthogonalMatrixGroup_generic, NamedMatrixGroup_gap, FinitelyGeneratedMatrixGroup_gap): - r""" - The general or special orthogonal group in GAP. - - TESTS: - - Check that :trac:`20867` is fixed:: - - sage: from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap - sage: G = GO(3,3) - sage: isinstance(G, FinitelyGeneratedMatrixGroup_gap) - True - """ - @cached_method - def invariant_bilinear_form(self): - """ - Return the symmetric bilinear form preserved by the orthogonal - group. - - OUTPUT: - - A matrix `M` such that, for every group element g, the - identity `g m g^T = m` holds. In characteristic different from - two, this uniquely determines the orthogonal group. - - EXAMPLES:: - - sage: G = GO(4, GF(7), -1) - sage: G.invariant_bilinear_form() - [0 1 0 0] - [1 0 0 0] - [0 0 2 0] - [0 0 0 2] - - sage: G = GO(4, GF(7), +1) - sage: G.invariant_bilinear_form() - [0 1 0 0] - [1 0 0 0] - [0 0 6 0] - [0 0 0 2] - - sage: G = SO(4, GF(7), -1) - sage: G.invariant_bilinear_form() - [0 1 0 0] - [1 0 0 0] - [0 0 2 0] - [0 0 0 2] - - TESTS:: - - sage: G.invariant_form() - [0 1 0 0] - [1 0 0 0] - [0 0 2 0] - [0 0 0 2] - """ - m = self.gap().InvariantBilinearForm()['matrix'].matrix() - m.set_immutable() - return m - - invariant_form = invariant_bilinear_form # alias (analogues to symplectic and unitary cases) - - @cached_method - def invariant_quadratic_form(self): - r""" - Return the quadratic form preserved by the orthogonal group. - - OUTPUT: - - The matrix `Q` defining "orthogonal" as follows. The matrix - determines a quadratic form `q` on the natural vector space - `V`, on which `G` acts, by `q(v) = v Q v^t`. A matrix `M` is - an element of the orthogonal group if `q(v) = q(v M)` for all - `v \in V`. - - EXAMPLES:: - - sage: G = GO(4, GF(7), -1) - sage: G.invariant_quadratic_form() - [0 1 0 0] - [0 0 0 0] - [0 0 1 0] - [0 0 0 1] - - sage: G = GO(4, GF(7), +1) - sage: G.invariant_quadratic_form() - [0 1 0 0] - [0 0 0 0] - [0 0 3 0] - [0 0 0 1] - - sage: G = GO(4, QQ) - sage: G.invariant_quadratic_form() - [1 0 0 0] - [0 1 0 0] - [0 0 1 0] - [0 0 0 1] - - sage: G = SO(4, GF(7), -1) - sage: G.invariant_quadratic_form() - [0 1 0 0] - [0 0 0 0] - [0 0 1 0] - [0 0 0 1] - - TESTS:: - - sage: GO(4, GF(7), -1).invariant_form() - [0 1 0 0] - [1 0 0 0] - [0 0 2 0] - [0 0 0 2] - """ - m = self.gap().InvariantQuadraticForm()['matrix'].matrix() - m.set_immutable() - return m diff --git a/src/sage/groups/matrix_gps/orthogonal_gap.py b/src/sage/groups/matrix_gps/orthogonal_gap.py new file mode 100644 index 00000000000..17dabb698f2 --- /dev/null +++ b/src/sage/groups/matrix_gps/orthogonal_gap.py @@ -0,0 +1,141 @@ +r""" +Orthogonal Linear Groups with GAP +""" + +# **************************************************************************** +# Copyright (C) 2006 David Joyner and William Stein +# 2009 Mike Hansen +# 2013 Volker Braun +# 2018 Sebastian Oehms +# 2018 Travis Scrimshaw +# 2023 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.named_group_gap import NamedMatrixGroup_gap +from sage.groups.matrix_gps.orthogonal import OrthogonalMatrixGroup_generic +from sage.misc.cachefunc import cached_method + + +class OrthogonalMatrixGroup_gap(OrthogonalMatrixGroup_generic, NamedMatrixGroup_gap, FinitelyGeneratedMatrixGroup_gap): + r""" + The general or special orthogonal group in GAP. + + TESTS: + + Check that :trac:`20867` is fixed:: + + sage: from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap + sage: G = GO(3,3) + sage: isinstance(G, FinitelyGeneratedMatrixGroup_gap) + True + """ + @cached_method + def invariant_bilinear_form(self): + """ + Return the symmetric bilinear form preserved by the orthogonal + group. + + OUTPUT: + + A matrix `M` such that, for every group element `g`, the + identity `g m g^T = m` holds. In characteristic different from + two, this uniquely determines the orthogonal group. + + EXAMPLES:: + + sage: G = GO(4, GF(7), -1) + sage: G.invariant_bilinear_form() + [0 1 0 0] + [1 0 0 0] + [0 0 2 0] + [0 0 0 2] + + sage: G = GO(4, GF(7), +1) + sage: G.invariant_bilinear_form() + [0 1 0 0] + [1 0 0 0] + [0 0 6 0] + [0 0 0 2] + + sage: G = SO(4, GF(7), -1) + sage: G.invariant_bilinear_form() + [0 1 0 0] + [1 0 0 0] + [0 0 2 0] + [0 0 0 2] + + TESTS:: + + sage: G.invariant_form() + [0 1 0 0] + [1 0 0 0] + [0 0 2 0] + [0 0 0 2] + """ + m = self.gap().InvariantBilinearForm()['matrix'].matrix() + m.set_immutable() + return m + + invariant_form = invariant_bilinear_form # alias (analogues to symplectic and unitary cases) + + @cached_method + def invariant_quadratic_form(self): + r""" + Return the quadratic form preserved by the orthogonal group. + + OUTPUT: + + The matrix `Q` defining "orthogonal" as follows. The matrix + determines a quadratic form `q` on the natural vector space + `V`, on which `G` acts, by `q(v) = v Q v^t`. A matrix `M` is + an element of the orthogonal group if `q(v) = q(v M)` for all + `v \in V`. + + EXAMPLES:: + + sage: G = GO(4, GF(7), -1) + sage: G.invariant_quadratic_form() + [0 1 0 0] + [0 0 0 0] + [0 0 1 0] + [0 0 0 1] + + sage: G = GO(4, GF(7), +1) + sage: G.invariant_quadratic_form() + [0 1 0 0] + [0 0 0 0] + [0 0 3 0] + [0 0 0 1] + + sage: G = GO(4, QQ) + sage: G.invariant_quadratic_form() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + + sage: G = SO(4, GF(7), -1) + sage: G.invariant_quadratic_form() + [0 1 0 0] + [0 0 0 0] + [0 0 1 0] + [0 0 0 1] + + TESTS:: + + sage: GO(4, GF(7), -1).invariant_form() + [0 1 0 0] + [1 0 0 0] + [0 0 2 0] + [0 0 0 2] + """ + m = self.gap().InvariantQuadraticForm()['matrix'].matrix() + m.set_immutable() + return m diff --git a/src/sage/groups/matrix_gps/pickling_overrides.py b/src/sage/groups/matrix_gps/pickling_overrides.py index 8bc887d87c0..9a62d03e6c4 100644 --- a/src/sage/groups/matrix_gps/pickling_overrides.py +++ b/src/sage/groups/matrix_gps/pickling_overrides.py @@ -4,8 +4,8 @@ from sage.structure.sage_object import register_unpickle_override -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap -from sage.groups.matrix_gps.group_element import MatrixGroupElement_gap +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.group_element_gap import MatrixGroupElement_gap from sage.groups.matrix_gps.linear import GL, LinearMatrixGroup_generic diff --git a/src/sage/groups/matrix_gps/symplectic.py b/src/sage/groups/matrix_gps/symplectic.py index 4dcc2f116ca..aad18def93b 100644 --- a/src/sage/groups/matrix_gps/symplectic.py +++ b/src/sage/groups/matrix_gps/symplectic.py @@ -3,17 +3,17 @@ EXAMPLES:: - sage: G = Sp(4,GF(7)); G + sage: G = Sp(4, GF(7)); G # optional - sage.rings.finite_rings Symplectic Group of degree 4 over Finite Field of size 7 - sage: g = prod(G.gens()); g + sage: g = prod(G.gens()); g # optional - sage.rings.finite_rings [3 0 3 0] [1 0 0 0] [0 1 0 1] [0 2 0 0] - sage: m = g.matrix() - sage: m * G.invariant_form() * m.transpose() == G.invariant_form() + sage: m = g.matrix() # optional - sage.rings.finite_rings + sage: m * G.invariant_form() * m.transpose() == G.invariant_form() # optional - sage.rings.finite_rings True - sage: G.order() + sage: G.order() # optional - sage.rings.finite_rings 276595200 AUTHORS: @@ -31,7 +31,9 @@ # **************************************************************************** # Copyright (C) 2006 David Joyner and William Stein -# Copyright (C) 2013 Volker Braun +# 2013 Volker Braun +# 2018 Sebastian Oehms +# 2018 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -45,8 +47,7 @@ from sage.rings.finite_rings.finite_field_base import FiniteField from sage.groups.matrix_gps.named_group import ( normalize_args_vectorspace, normalize_args_invariant_form, - NamedMatrixGroup_generic, NamedMatrixGroup_gap) -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap + NamedMatrixGroup_generic) ############################################################################### @@ -81,13 +82,13 @@ def Sp(n, R, var='a', invariant_form=None): EXAMPLES:: - sage: Sp(4, 5) + sage: Sp(4, 5) # optional - sage.rings.finite_rings Symplectic Group of degree 4 over Finite Field of size 5 sage: Sp(4, IntegerModRing(15)) Symplectic Group of degree 4 over Ring of integers modulo 15 - sage: Sp(3, GF(7)) + sage: Sp(3, GF(7)) # optional - sage.rings.finite_rings Traceback (most recent call last): ... ValueError: the degree must be even @@ -109,14 +110,14 @@ def Sp(n, R, var='a', invariant_form=None): [ 0 0 0 2] [-1 0 0 0] [ 0 -2 0 0] - sage: pm = Permutation([2,1,4,3]).to_matrix() - sage: g = Sp4(pm); g in Sp4; g + sage: pm = Permutation([2,1,4,3]).to_matrix() # optional - sage.combinat + sage: g = Sp4(pm); g in Sp4; g # optional - sage.combinat True [0 1 0 0] [1 0 0 0] [0 0 0 1] [0 0 1 0] - sage: Sp4m(pm) + sage: Sp4m(pm) # optional - sage.combinat Traceback (most recent call last): ... TypeError: matrix must be symplectic with respect to the alternating form @@ -125,7 +126,7 @@ def Sp(n, R, var='a', invariant_form=None): [-1 0 0 0] [ 0 -2 0 0] - sage: Sp(4,3, invariant_form=[[0,0,0,1],[0,0,1,0],[0,2,0,0], [2,0,0,0]]) + sage: Sp(4,3, invariant_form=[[0,0,0,1],[0,0,1,0],[0,2,0,0], [2,0,0,0]]) # optional - sage.rings.finite_rings Traceback (most recent call last): ... NotImplementedError: invariant_form for finite groups is fixed by GAP @@ -134,11 +135,11 @@ def Sp(n, R, var='a', invariant_form=None): sage: TestSuite(Sp4).run() sage: TestSuite(Sp4m).run() - sage: groups.matrix.Sp(2, 3) + sage: groups.matrix.Sp(2, 3) # optional - sage.rings.finite_rings Symplectic Group of degree 2 over Finite Field of size 3 - sage: G = Sp(4,5) - sage: TestSuite(G).run() + sage: G = Sp(4,5) # optional - sage.rings.finite_rings + sage: TestSuite(G).run() # optional - sage.rings.finite_rings """ degree, ring = normalize_args_vectorspace(n, R, var=var) if degree % 2: @@ -161,10 +162,17 @@ def Sp(n, R, var='a', invariant_form=None): ltx = r'\text{{Sp}}_{{{0}}}({1})'.format(degree, latex(ring)) try: - cmd = 'Sp({0}, {1})'.format(degree, ring._gap_init_()) - return SymplecticMatrixGroup_gap(degree, ring, True, name, ltx, cmd) - except ValueError: - return SymplecticMatrixGroup_generic(degree, ring, True, name, ltx, invariant_form=invariant_form) + from .symplectic_gap import SymplecticMatrixGroup_gap + except ImportError: + pass + else: + try: + cmd = 'Sp({0}, {1})'.format(degree, ring._gap_init_()) + return SymplecticMatrixGroup_gap(degree, ring, True, name, ltx, cmd) + except ValueError: + pass + + return SymplecticMatrixGroup_generic(degree, ring, True, name, ltx, invariant_form=invariant_form) class SymplecticMatrixGroup_generic(NamedMatrixGroup_generic): @@ -173,13 +181,15 @@ class SymplecticMatrixGroup_generic(NamedMatrixGroup_generic): EXAMPLES:: - sage: Sp43 = Sp(4,3); Sp43 + sage: Sp43 = Sp(4,3); Sp43 # optional - sage.rings.finite_rings Symplectic Group of degree 4 over Finite Field of size 3 - sage: latex(Sp43) + sage: latex(Sp43) # optional - sage.rings.finite_rings \text{Sp}_{4}(\Bold{F}_{3}) - sage: Sp4m = Sp(4,QQ, invariant_form=(0, 0, 1, 0, 0, 0, 0, 2, -1, 0, 0, 0, 0, -2, 0, 0)); Sp4m - Symplectic Group of degree 4 over Rational Field with respect to alternating bilinear form + sage: Sp4m = Sp(4, QQ, invariant_form=(0, 0, 1, 0, 0, 0, 0, 2, + ....: -1, 0, 0, 0, 0, -2, 0, 0)); Sp4m + Symplectic Group of degree 4 over Rational Field + with respect to alternating bilinear form [ 0 0 1 0] [ 0 0 0 2] [-1 0 0 0] @@ -198,9 +208,7 @@ def invariant_form(self): """ Return the quadratic form preserved by the symplectic group. - OUTPUT: - - A matrix. + OUTPUT: A matrix. EXAMPLES:: @@ -231,53 +239,9 @@ def _check_matrix(self, x, *args): EXAMPLES:: - sage: G = Sp(4,GF(5)) - sage: G._check_matrix(G.an_element().matrix()) + sage: G = Sp(4, GF(5)) # optional - sage.rings.finite_rings + sage: G._check_matrix(G.an_element().matrix()) # optional - sage.rings.finite_rings """ F = self.invariant_form() if x * F * x.transpose() != F: raise TypeError('matrix must be symplectic with respect to the alternating form\n{}'.format(F)) - - -class SymplecticMatrixGroup_gap(SymplecticMatrixGroup_generic, NamedMatrixGroup_gap, FinitelyGeneratedMatrixGroup_gap): - r""" - Symplectic group in GAP. - - EXAMPLES:: - - sage: Sp(2,4) - Symplectic Group of degree 2 over Finite Field in a of size 2^2 - - sage: latex(Sp(4,5)) - \text{Sp}_{4}(\Bold{F}_{5}) - - TESTS: - - Check that :trac:`20867` is fixed:: - - sage: from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap - sage: G = Sp(4,3) - sage: isinstance(G, FinitelyGeneratedMatrixGroup_gap) - True - """ - - @cached_method - def invariant_form(self): - """ - Return the quadratic form preserved by the symplectic group. - - OUTPUT: - - A matrix. - - EXAMPLES:: - - sage: Sp(4, GF(3)).invariant_form() - [0 0 0 1] - [0 0 1 0] - [0 2 0 0] - [2 0 0 0] - """ - m = self.gap().InvariantBilinearForm()['matrix'].matrix() - m.set_immutable() - return m diff --git a/src/sage/groups/matrix_gps/symplectic_gap.py b/src/sage/groups/matrix_gps/symplectic_gap.py new file mode 100644 index 00000000000..adeefea218f --- /dev/null +++ b/src/sage/groups/matrix_gps/symplectic_gap.py @@ -0,0 +1,63 @@ +""" +Symplectic Linear Groups with GAP +""" + +# **************************************************************************** +# Copyright (C) 2006 David Joyner and William Stein +# 2013 Volker Braun +# 2018 Travis Scrimshaw +# 2023 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.named_group_gap import NamedMatrixGroup_gap +from sage.groups.matrix_gps.symplectic import SymplecticMatrixGroup_generic +from sage.misc.cachefunc import cached_method + + +class SymplecticMatrixGroup_gap(SymplecticMatrixGroup_generic, NamedMatrixGroup_gap, FinitelyGeneratedMatrixGroup_gap): + r""" + Symplectic group in GAP. + + EXAMPLES:: + + sage: Sp(2,4) + Symplectic Group of degree 2 over Finite Field in a of size 2^2 + + sage: latex(Sp(4,5)) + \text{Sp}_{4}(\Bold{F}_{5}) + + TESTS: + + Check that :trac:`20867` is fixed:: + + sage: from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap + sage: G = Sp(4,3) + sage: isinstance(G, FinitelyGeneratedMatrixGroup_gap) + True + """ + + @cached_method + def invariant_form(self): + """ + Return the quadratic form preserved by the symplectic group. + + OUTPUT: A matrix. + + EXAMPLES:: + + sage: Sp(4, GF(3)).invariant_form() + [0 0 0 1] + [0 0 1 0] + [0 2 0 0] + [2 0 0 0] + """ + m = self.gap().InvariantBilinearForm()['matrix'].matrix() + m.set_immutable() + return m diff --git a/src/sage/groups/matrix_gps/unitary.py b/src/sage/groups/matrix_gps/unitary.py index 08ecfea36bb..8d8a4d23491 100644 --- a/src/sage/groups/matrix_gps/unitary.py +++ b/src/sage/groups/matrix_gps/unitary.py @@ -6,18 +6,18 @@ EXAMPLES:: - sage: G = SU(3,5) - sage: G.order() + sage: G = SU(3,5) # optional - sage.rings.finite_rings + sage: G.order() # optional - sage.rings.finite_rings 378000 - sage: G + sage: G # optional - sage.rings.finite_rings Special Unitary Group of degree 3 over Finite Field in a of size 5^2 - sage: G.gens() + sage: G.gens() # optional - sage.rings.finite_rings ( [ a 0 0] [4*a 4 1] [ 0 2*a + 2 0] [ 4 4 0] [ 0 0 3*a], [ 1 0 0] ) - sage: G.base_ring() + sage: G.base_ring() # optional - sage.rings.finite_rings Finite Field in a of size 5^2 AUTHORS: @@ -40,7 +40,12 @@ # **************************************************************************** # Copyright (C) 2006 David Joyner and William Stein -# Copyright (C) 2013 Volker Braun +# 2012 Rob Beezer +# 2013 Volker Braun +# 2018 Sebastian Oehms +# 2018 Travis Scrimshaw +# 2019 Jeroen Demeyer +# 2023 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -55,26 +60,23 @@ from sage.misc.cachefunc import cached_method from sage.groups.matrix_gps.named_group import ( normalize_args_vectorspace, normalize_args_invariant_form, - NamedMatrixGroup_generic, NamedMatrixGroup_gap ) -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap + NamedMatrixGroup_generic) def finite_field_sqrt(ring): """ Helper function. - INPUT: - - A ring. + INPUT: A ring. OUTPUT: - Integer q such that ``ring`` is the finite field with `q^2` elements. + Integer `q` such that ``ring`` is the finite field with `q^2` elements. EXAMPLES:: sage: from sage.groups.matrix_gps.unitary import finite_field_sqrt - sage: finite_field_sqrt(GF(4, 'a')) + sage: finite_field_sqrt(GF(4, 'a')) # optional - sage.rings.finite_rings 2 """ if not isinstance(ring, FiniteField): @@ -97,7 +99,7 @@ def _UG(n, R, special, var='a', invariant_form=None): TESTS:: - sage: GU(3,25).order() # indirect doctest + sage: GU(3,25).order() # indirect doctest # optional - sage.rings.finite_rings 3961191000000 """ prefix = 'General' @@ -135,10 +137,16 @@ def _UG(n, R, special, var='a', invariant_form=None): ltx = r'\text{{{0}U}}_{{{1}}}({2})'.format(latex_prefix, degree, latex(ring)) if isinstance(ring, FiniteField): - cmd = '{0}U({1}, {2})'.format(latex_prefix, degree, q) - return UnitaryMatrixGroup_gap(degree, ring, special, name, ltx, cmd) - else: - return UnitaryMatrixGroup_generic(degree, ring, special, name, ltx, invariant_form=invariant_form) + try: + from .unitary_gap import UnitaryMatrixGroup_gap + except ImportError: + pass + else: + cmd = '{0}U({1}, {2})'.format(latex_prefix, degree, q) + return UnitaryMatrixGroup_gap(degree, ring, special, name, ltx, cmd) + + return UnitaryMatrixGroup_generic(degree, ring, special, name, ltx, + invariant_form=invariant_form) ############################################################################### @@ -155,9 +163,9 @@ def GU(n, R, var='a', invariant_form=None): .. NOTE:: - For a finite field the matrices that preserve a sesquilinear - form over `F_q` live over `F_{q^2}`. So ``GU(n,q)`` for - a prime power ``q`` constructs the matrix group over the base + For a finite field, the matrices that preserve a sesquilinear + form over `\GF{q}` live over `\GF{q^2}`. So ``GU(n,q)`` for + a prime power `q` constructs the matrix group over the base ring ``GF(q^2)``. .. NOTE:: @@ -176,31 +184,29 @@ def GU(n, R, var='a', invariant_form=None): - ``invariant_form`` -- (optional) instances being accepted by the matrix-constructor which define a `n \times n` square matrix - over R describing the hermitian form to be kept invariant + over `R` describing the hermitian form to be kept invariant by the unitary group; the form is checked to be non-degenerate and hermitian but not to be positive definite - OUTPUT: - - Return the general unitary group. + OUTPUT: The general unitary group. EXAMPLES:: - sage: G = GU(3, 7); G + sage: G = GU(3, 7); G # optional - sage.rings.finite_rings General Unitary Group of degree 3 over Finite Field in a of size 7^2 - sage: G.gens() + sage: G.gens() # optional - sage.rings.finite_rings ( [ a 0 0] [6*a 6 1] [ 0 1 0] [ 6 6 0] [ 0 0 5*a], [ 1 0 0] ) - sage: GU(2,QQ) + sage: GU(2, QQ) General Unitary Group of degree 2 over Rational Field - sage: G = GU(3, 5, var='beta') - sage: G.base_ring() + sage: G = GU(3, 5, var='beta') # optional - sage.rings.finite_rings + sage: G.base_ring() # optional - sage.rings.finite_rings Finite Field in beta of size 5^2 - sage: G.gens() + sage: G.gens() # optional - sage.rings.finite_rings ( [ beta 0 0] [4*beta 4 1] [ 0 1 0] [ 4 4 0] @@ -209,27 +215,27 @@ def GU(n, R, var='a', invariant_form=None): Using the ``invariant_form`` option:: - sage: UCF = UniversalCyclotomicField(); e5=UCF.gen(5) - sage: m = matrix(UCF, 3,3, [[1,e5,0],[e5.conjugate(),2,0],[0,0,1]]) - sage: G = GU(3, UCF) - sage: Gm = GU(3, UCF, invariant_form=m) - sage: G == Gm + sage: UCF = UniversalCyclotomicField(); e5 = UCF.gen(5) # optional - sage.rings.number_field + sage: m = matrix(UCF, 3, 3, [[1,e5,0], [e5.conjugate(),2,0], [0,0,1]]) # optional - sage.rings.number_field + sage: G = GU(3, UCF) # optional - sage.rings.number_field + sage: Gm = GU(3, UCF, invariant_form=m) # optional - sage.rings.number_field + sage: G == Gm # optional - sage.rings.number_field False - sage: G.invariant_form() + sage: G.invariant_form() # optional - sage.rings.number_field [1 0 0] [0 1 0] [0 0 1] - sage: Gm.invariant_form() + sage: Gm.invariant_form() # optional - sage.rings.number_field [ 1 E(5) 0] [E(5)^4 2 0] [ 0 0 1] - sage: pm = Permutation((1,2,3)).to_matrix() - sage: g = G(pm); g in G; g + sage: pm = Permutation((1,2,3)).to_matrix() # optional - sage.combinat sage.rings.number_field + sage: g = G(pm); g in G; g # optional - sage.combinat sage.rings.number_field True [0 0 1] [1 0 0] [0 1 0] - sage: Gm(pm) + sage: Gm(pm) # optional - sage.combinat sage.rings.number_field Traceback (most recent call last): ... TypeError: matrix must be unitary with respect to the hermitian form @@ -237,20 +243,20 @@ def GU(n, R, var='a', invariant_form=None): [E(5)^4 2 0] [ 0 0 1] - sage: GU(3,3, invariant_form=[[1,0,0],[0,2,0],[0,0,1]]) + sage: GU(3, 3, invariant_form=[[1,0,0], [0,2,0], [0,0,1]]) # optional - sage.rings.number_field Traceback (most recent call last): ... NotImplementedError: invariant_form for finite groups is fixed by GAP - sage: GU(2,QQ, invariant_form=[[1,0],[2,0]]) + sage: GU(2, QQ, invariant_form=[[1,0], [2,0]]) Traceback (most recent call last): ... ValueError: invariant_form must be non-degenerate TESTS:: - sage: TestSuite(G).run() - sage: groups.matrix.GU(2, 3) + sage: TestSuite(G).run() # optional - sage.rings.number_field + sage: groups.matrix.GU(2, 3) # optional - sage.groups sage.rings.finite_rings General Unitary Group of degree 2 over Finite Field in a of size 3^2 """ return _UG(n, R, False, var=var, invariant_form=invariant_form) @@ -269,8 +275,8 @@ def SU(n, R, var='a', invariant_form=None): .. NOTE:: For a finite field the matrices that preserve a sesquilinear - form over `F_q` live over `F_{q^2}`. So ``SU(n,q)`` for - a prime power ``q`` constructs the matrix group over the base + form over `\GF{q}` live over `\GF{q^2}`. So ``SU(n,q)`` for + a prime power `q` constructs the matrix group over the base ring ``GF(q^2)``. .. NOTE:: @@ -299,35 +305,35 @@ def SU(n, R, var='a', invariant_form=None): EXAMPLES:: - sage: SU(3,5) + sage: SU(3,5) # optional - sage.rings.finite_rings Special Unitary Group of degree 3 over Finite Field in a of size 5^2 - sage: SU(3, GF(5)) + sage: SU(3, GF(5)) # optional - sage.rings.finite_rings Special Unitary Group of degree 3 over Finite Field in a of size 5^2 - sage: SU(3,QQ) + sage: SU(3, QQ) Special Unitary Group of degree 3 over Rational Field Using the ``invariant_form`` option:: - sage: CF3 = CyclotomicField(3); e3 = CF3.gen() - sage: m = matrix(CF3, 3,3, [[1,e3,0],[e3.conjugate(),2,0],[0,0,1]]) - sage: G = SU(3, CF3) - sage: Gm = SU(3, CF3, invariant_form=m) - sage: G == Gm + sage: CF3 = CyclotomicField(3); e3 = CF3.gen() # optional - sage.rings.number_field + sage: m = matrix(CF3, 3, 3, [[1,e3,0], [e3.conjugate(),2,0], [0,0,1]]) # optional - sage.rings.number_field + sage: G = SU(3, CF3) # optional - sage.rings.number_field + sage: Gm = SU(3, CF3, invariant_form=m) # optional - sage.rings.number_field + sage: G == Gm # optional - sage.rings.number_field False - sage: G.invariant_form() + sage: G.invariant_form() # optional - sage.rings.number_field [1 0 0] [0 1 0] [0 0 1] - sage: Gm.invariant_form() + sage: Gm.invariant_form() # optional - sage.rings.number_field [ 1 zeta3 0] [-zeta3 - 1 2 0] [ 0 0 1] - sage: pm = Permutation((1,2,3)).to_matrix() - sage: G(pm) + sage: pm = Permutation((1,2,3)).to_matrix() # optional - sage.combinat sage.rings.number_field + sage: G(pm) # optional - sage.combinat sage.rings.number_field [0 0 1] [1 0 0] [0 1 0] - sage: Gm(pm) + sage: Gm(pm) # optional - sage.combinat sage.rings.number_field Traceback (most recent call last): ... TypeError: matrix must be unitary with respect to the hermitian form @@ -335,15 +341,15 @@ def SU(n, R, var='a', invariant_form=None): [-zeta3 - 1 2 0] [ 0 0 1] - sage: SU(3,5, invariant_form=[[1,0,0],[0,2,0],[0,0,3]]) + sage: SU(3, 5, invariant_form=[[1,0,0], [0,2,0], [0,0,3]]) # optional - sage.rings.finite_rings Traceback (most recent call last): ... NotImplementedError: invariant_form for finite groups is fixed by GAP TESTS:: - sage: TestSuite(Gm).run() - sage: groups.matrix.SU(2, 3) + sage: TestSuite(Gm).run() # optional - sage.rings.number_field + sage: groups.matrix.SU(2, 3) # optional - sage.rings.finite_rings Special Unitary Group of degree 2 over Finite Field in a of size 3^2 """ return _UG(n, R, True, var=var, invariant_form=invariant_form) @@ -359,20 +365,20 @@ class UnitaryMatrixGroup_generic(NamedMatrixGroup_generic): EXAMPLES:: - sage: G = GU(3, GF(7)); G + sage: G = GU(3, GF(7)); G # optional - sage.rings.finite_rings General Unitary Group of degree 3 over Finite Field in a of size 7^2 - sage: latex(G) + sage: latex(G) # optional - sage.rings.finite_rings \text{GU}_{3}(\Bold{F}_{7^{2}}) - sage: G = SU(3, GF(5)); G + sage: G = SU(3, GF(5)); G # optional - sage.rings.finite_rings Special Unitary Group of degree 3 over Finite Field in a of size 5^2 - sage: latex(G) + sage: latex(G) # optional - sage.rings.finite_rings \text{SU}_{3}(\Bold{F}_{5^{2}}) - sage: CF3 = CyclotomicField(3); e3 = CF3.gen() - sage: m = matrix(CF3, 3,3, [[1,e3,0],[e3.conjugate(),2,0],[0,0,1]]) - sage: G = SU(3, CF3, invariant_form=m) - sage: latex(G) + sage: CF3 = CyclotomicField(3); e3 = CF3.gen() # optional - sage.rings.number_field + sage: m = matrix(CF3, 3, 3, [[1,e3,0], [e3.conjugate(),2,0], [0,0,1]]) # optional - sage.rings.number_field + sage: G = SU(3, CF3, invariant_form=m) # optional - sage.rings.number_field + sage: latex(G) # optional - sage.rings.number_field \text{SU}_{3}(\Bold{Q}(\zeta_{3}))\text{ with respect to positive definite hermitian form }\left(\begin{array}{rrr} 1 & \zeta_{3} & 0 \\ -\zeta_{3} - 1 & 2 & 0 \\ @@ -386,13 +392,11 @@ def invariant_form(self): Return the hermitian form preserved by the unitary group. - OUTPUT: - - A square matrix describing the bilinear form + OUTPUT: A square matrix describing the bilinear form EXAMPLES:: - sage: SU4 = SU(4,QQ) + sage: SU4 = SU(4, QQ) sage: SU4.invariant_form() [1 0 0 0] [0 1 0 0] @@ -416,10 +420,10 @@ def _check_matrix(self, x, *args): EXAMPLES:: - sage: G = GU(2, GF(5)) - sage: G._check_matrix(G.an_element().matrix()) - sage: G = SU(2, GF(5)) - sage: G._check_matrix(G.an_element().matrix()) + sage: G = GU(2, GF(5)) # optional - sage.rings.finite_rings + sage: G._check_matrix(G.an_element().matrix()) # optional - sage.rings.finite_rings + sage: G = SU(2, GF(5)) # optional - sage.rings.finite_rings + sage: G._check_matrix(G.an_element().matrix()) # optional - sage.rings.finite_rings """ if self._special and x.determinant() != 1: raise TypeError('matrix must have determinant one') @@ -430,43 +434,3 @@ def _check_matrix(self, x, *args): raise TypeError('matrix must be unitary') else: raise TypeError('matrix must be unitary with respect to the hermitian form\n{}'.format(H)) - -class UnitaryMatrixGroup_gap(UnitaryMatrixGroup_generic, NamedMatrixGroup_gap, FinitelyGeneratedMatrixGroup_gap): - r""" - The general or special unitary group in GAP. - - TESTS: - - Check that :trac:`20867` is fixed:: - - sage: from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap - sage: G = GU(3,3) - sage: isinstance(G, FinitelyGeneratedMatrixGroup_gap) - True - """ - - @cached_method - def invariant_form(self): - """ - Return the hermitian form preserved by the unitary group. - - OUTPUT: - - A square matrix describing the bilinear form - - EXAMPLES:: - - sage: G32=GU(3,2) - sage: G32.invariant_form() - [0 0 1] - [0 1 0] - [1 0 0] - """ - d = self.degree() - R = self.base_ring() - # note that self.gap().InvariantSesquilinearForm()['matrix'].matrix().base_ring() != R for example for self = GU(3.2) - # therefore we have to coerce into the right matrix space - from sage.matrix.constructor import matrix - m = matrix(R, d, d, self.gap().InvariantSesquilinearForm()['matrix'].matrix()) - m.set_immutable() - return m diff --git a/src/sage/groups/matrix_gps/unitary_gap.py b/src/sage/groups/matrix_gps/unitary_gap.py new file mode 100644 index 00000000000..02b0456decb --- /dev/null +++ b/src/sage/groups/matrix_gps/unitary_gap.py @@ -0,0 +1,62 @@ +r""" +Unitary Groups `GU(n,q)` and `SU(n,q)` with GAP +""" + +# **************************************************************************** +# Copyright (C) 2006 David Joyner and William Stein +# 2018 Sebastian Oehms +# 2018 Travis Scrimshaw +# 2023 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap +from sage.groups.matrix_gps.named_group_gap import NamedMatrixGroup_gap +from sage.groups.matrix_gps.unitary import UnitaryMatrixGroup_generic +from sage.misc.cachefunc import cached_method + + +class UnitaryMatrixGroup_gap(UnitaryMatrixGroup_generic, NamedMatrixGroup_gap, FinitelyGeneratedMatrixGroup_gap): + r""" + The general or special unitary group in GAP. + + TESTS: + + Check that :trac:`20867` is fixed:: + + sage: from sage.groups.matrix_gps.finitely_generated_gap import FinitelyGeneratedMatrixGroup_gap + sage: G = GU(3,3) + sage: isinstance(G, FinitelyGeneratedMatrixGroup_gap) + True + """ + + @cached_method + def invariant_form(self): + """ + Return the hermitian form preserved by the unitary group. + + OUTPUT: + + A square matrix describing the bilinear form + + EXAMPLES:: + + sage: G32 = GU(3,2) + sage: G32.invariant_form() + [0 0 1] + [0 1 0] + [1 0 0] + """ + d = self.degree() + R = self.base_ring() + # note that self.gap().InvariantSesquilinearForm()['matrix'].matrix().base_ring() != R for example for self = GU(3.2) + # therefore we have to coerce into the right matrix space + from sage.matrix.constructor import matrix + m = matrix(R, d, d, self.gap().InvariantSesquilinearForm()['matrix'].matrix()) + m.set_immutable() + return m diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 9e4cf4d582e..a2583f7497a 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -1230,12 +1230,24 @@ def _coerce_map_from_(self, S): pass else: MS = meth_matrix_space() - from sage.groups.matrix_gps.matrix_group import is_MatrixGroup - from sage.modular.arithgroup.arithgroup_generic import is_ArithmeticSubgroup - if is_MatrixGroup(S) or is_ArithmeticSubgroup(S): - return self.has_coerce_map_from(MS) + + try: + from sage.groups.matrix_gps.matrix_group import is_MatrixGroup + except ImportError: + pass else: - return False + if is_MatrixGroup(S): + return self.has_coerce_map_from(MS) + + try: + from sage.modular.arithgroup.arithgroup_generic import is_ArithmeticSubgroup + except ImportError: + pass + else: + if is_ArithmeticSubgroup(S): + return self.has_coerce_map_from(MS) + + return False # The parent is not matrix-like: coerce via base ring return (self.nrows() == self.ncols()) and self._coerce_map_via([B], S) diff --git a/src/sage/modular/arithgroup/congroup_generic.py b/src/sage/modular/arithgroup/congroup_generic.py index 75605b1c3dc..e71426f9922 100644 --- a/src/sage/modular/arithgroup/congroup_generic.py +++ b/src/sage/modular/arithgroup/congroup_generic.py @@ -22,7 +22,6 @@ ################################################################################ from sage.arith.misc import gcd -from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.matrix.matrix_space import MatrixSpace from sage.misc.misc_c import prod from sage.rings.finite_rings.integer_mod_ring import Zmod @@ -88,7 +87,9 @@ def CongruenceSubgroup_constructor(*args): ... TypeError: Ring of definition must be Z / NZ for some N """ + from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.groups.matrix_gps.matrix_group import is_MatrixGroup + if is_MatrixGroup(args[0]): G = args[0] @@ -377,6 +378,8 @@ def to_even_subgroup(self): if self.is_even(): return self else: + from sage.groups.matrix_gps.finitely_generated import MatrixGroup + G = self.image_mod_n() H = MatrixGroup([ g.matrix() for g in G.gens()] + [G.matrix_space()(-1)]) return CongruenceSubgroup_constructor(H) @@ -590,7 +593,9 @@ def _minimize_level(G): sage: sage.modular.arithgroup.congroup_generic._minimize_level(G) 3 """ + from sage.groups.matrix_gps.finitely_generated import MatrixGroup from .congroup_gamma import Gamma_constructor as Gamma + Glist = list(G) N = G.base_ring().characteristic() i = Gamma(N).index()