def _minimize_level(G): r""" Utility function. Given a matrix group `G` contained in `SL(2, \ZZ / N\ZZ)` for some `N`, test whether or not `G` is the preimage of a subgroup of smaller level, and if so, return that subgroup. The trivial group is handled specially: instead of returning a group, it returns an integer `N`, representing the trivial subgroup of `SL(2, \ZZ / N\ZZ)`. EXAMPLE:: sage: M = MatrixSpace(Zmod(9), 2, 2) sage: G = MatrixGroup([M(x) for x in [[1,1,0,1],[1,3,0,1],[1,0,3,1],[4,0,0,7]]]); G Matrix group over Ring of integers modulo 9 with 4 generators ( [1 1] [1 3] [1 0] [4 0] [0 1], [0 1], [3 1], [0 7] ) sage: sage.modular.arithgroup.congroup_generic._minimize_level(G) Matrix group over Ring of integers modulo 3 with 1 generators ( [1 1] [0 1] ) sage: G = MatrixGroup([M(x) for x in [[1,3,0,1],[1,0,3,1],[4,0,0,7]]]); G Matrix group over Ring of integers modulo 9 with 3 generators ( [1 3] [1 0] [4 0] [0 1], [3 1], [0 7] ) sage: sage.modular.arithgroup.congroup_generic._minimize_level(G) 3 """ from congroup_gamma import Gamma_constructor as Gamma Glist = list(G) N = G.base_ring().characteristic() i = Gamma(N).index() for p in N.prime_divisors(): j = Gamma(N // p).index() k = len( [g for g in Glist if g.matrix().change_ring(Zmod(N // p)) == 1]) if k == i // j: if N // p == 1: return ZZ(1) H = MatrixGroup( [g.matrix().change_ring(Zmod(N // p)) for g in G.gens()]) return _minimize_level(H) # now sanitize the generators (remove duplicates and copies of the identity) new_gens = [x.matrix() for x in G.gens() if x.matrix() != 1] all([x.set_immutable() for x in new_gens]) new_gens = list(Set(new_gens)) if new_gens == []: return ZZ(G.base_ring().characteristic()) return MatrixGroup(new_gens)
def _minimize_level(G): r""" Utility function. Given a matrix group `G` contained in `SL(2, \ZZ / N\ZZ)` for some `N`, test whether or not `G` is the preimage of a subgroup of smaller level, and if so, return that subgroup. The trivial group is handled specially: instead of returning a group, it returns an integer `N`, representing the trivial subgroup of `SL(2, \ZZ / N\ZZ)`. EXAMPLE:: sage: M = MatrixSpace(Zmod(9), 2, 2) sage: G = MatrixGroup([M(x) for x in [[1,1,0,1],[1,3,0,1],[1,0,3,1],[4,0,0,7]]]); G Matrix group over Ring of integers modulo 9 with 4 generators ( [1 1] [1 3] [1 0] [4 0] [0 1], [0 1], [3 1], [0 7] ) sage: sage.modular.arithgroup.congroup_generic._minimize_level(G) Matrix group over Ring of integers modulo 3 with 1 generators ( [1 1] [0 1] ) sage: G = MatrixGroup([M(x) for x in [[1,3,0,1],[1,0,3,1],[4,0,0,7]]]); G Matrix group over Ring of integers modulo 9 with 3 generators ( [1 3] [1 0] [4 0] [0 1], [3 1], [0 7] ) sage: sage.modular.arithgroup.congroup_generic._minimize_level(G) 3 """ from .congroup_gamma import Gamma_constructor as Gamma Glist = list(G) N = G.base_ring().characteristic() i = Gamma(N).index() for d in N.divisors()[:-1]: j = Gamma(d).index() k = len([g for g in Glist if g.matrix().change_ring(Zmod(d)) == 1]) if k == i // j: if d == 1: return ZZ(1) G = MatrixGroup([g.matrix().change_ring(Zmod(d)) for g in G.gens()]) N = d break # now sanitize the generators (remove duplicates and copies of the identity) new_gens = [x.matrix() for x in G.gens() if x.matrix() != 1] all([x.set_immutable() for x in new_gens]) new_gens = list(Set(new_gens)) if new_gens == []: return ZZ(N) return MatrixGroup(new_gens)
def pushforward(self, J, *args, **kwds): """ The image of an element or a subgroup. INPUT: ``J`` -- a subgroup or an element of the domain of ``self`` OUTPUT: The image of ``J`` under ``self``. .. NOTE:: ``pushforward`` is the method that is used when a map is called on anything that is not an element of its domain. For historical reasons, we keep the alias ``image()`` for this method. EXAMPLES:: sage: F = GF(7); MS = MatrixSpace(F,2,2) sage: F.multiplicative_generator() 3 sage: G = MatrixGroup([MS([3,0,0,1])]) sage: a = G.gens()[0]^2 sage: phi = G.hom([a]) sage: phi.image(G.gens()[0]) # indirect doctest [2 0] [0 1] sage: H = MatrixGroup([MS(a.list())]) sage: H Matrix group over Finite Field of size 7 with 1 generators ( [2 0] [0 1] ) The following tests against :trac:`10659`:: sage: phi(H) # indirect doctest Matrix group over Finite Field of size 7 with 1 generators ( [4 0] [0 1] ) """ phi = self.gap() F = self.codomain().base_ring() gapJ = to_libgap(J) if gapJ.IsGroup(): from sage.groups.matrix_gps.all import MatrixGroup img_gens = [ x.matrix(F) for x in phi.Image(gapJ).GeneratorsOfGroup() ] return MatrixGroup(img_gens) C = self.codomain() return C(phi.Image(gapJ).matrix(F))
def to_even_subgroup(self): r""" Return the smallest even subgroup of `SL(2, \ZZ)` containing self. EXAMPLES:: sage: G = Gamma(3) sage: G.to_even_subgroup() Congruence subgroup of SL(2,Z) of level 3, preimage of: Matrix group over Ring of integers modulo 3 with 1 generators ( [2 0] [0 2] ) """ if self.is_even(): return self else: G = self.image_mod_n() H = MatrixGroup([ g.matrix() for g in G.gens()] + [G.matrix_space()(-1)]) return CongruenceSubgroup_constructor(H)
def kernel(self): """ Return the kernel of ``self``, i.e., a matrix group. EXAMPLES:: sage: F = GF(7); MS = MatrixSpace(F,2,2) sage: F.multiplicative_generator() 3 sage: G = MatrixGroup([MS([3,0,0,1])]) sage: a = G.gens()[0]^2 sage: phi = G.hom([a]) sage: phi.kernel() Matrix group over Finite Field of size 7 with 1 generators ( [6 0] [0 1] ) """ gap_ker = self.gap().Kernel() F = self.domain().base_ring() from sage.groups.matrix_gps.all import MatrixGroup return MatrixGroup([x.matrix(F) for x in gap_ker.GeneratorsOfGroup()])
def CongruenceSubgroup_constructor(*args): r""" Attempt to create a congruence subgroup from the given data. The allowed inputs are as follows: - A :class:`~sage.groups.matrix_gps.matrix_group.MatrixGroup` object. This must be a group of matrices over `\ZZ / N\ZZ` for some `N`, with determinant 1, in which case the function will return the group of matrices in `SL(2, \ZZ)` whose reduction mod `N` is in the given group. - A list of matrices over `\ZZ / N\ZZ` for some `N`. The function will then compute the subgroup of `SL(2, \ZZ)` generated by these matrices, and proceed as above. - An integer `N` and a list of matrices (over any ring coercible to `\ZZ / N\ZZ`, e.g. over `\ZZ`). The matrices will then be coerced to `\ZZ / N\ZZ`. The function checks that the input G is valid. It then tests to see if `G` is the preimage mod `N` of some group of matrices modulo a proper divisor `M` of `N`, in which case it replaces `G` with this group before continuing. EXAMPLES:: sage: from sage.modular.arithgroup.congroup_generic import CongruenceSubgroup_constructor as CS sage: CS(2, [[1,1,0,1]]) Congruence subgroup of SL(2,Z) of level 2, preimage of: Matrix group over Ring of integers modulo 2 with 1 generators ( [1 1] [0 1] ) sage: CS([matrix(Zmod(2), 2, [1,1,0,1])]) Congruence subgroup of SL(2,Z) of level 2, preimage of: Matrix group over Ring of integers modulo 2 with 1 generators ( [1 1] [0 1] ) sage: CS(MatrixGroup([matrix(Zmod(2), 2, [1,1,0,1])])) Congruence subgroup of SL(2,Z) of level 2, preimage of: Matrix group over Ring of integers modulo 2 with 1 generators ( [1 1] [0 1] ) sage: CS(SL(2, 2)) Modular Group SL(2,Z) Some invalid inputs:: sage: CS(SU(2, 7)) Traceback (most recent call last): ... TypeError: Ring of definition must be Z / NZ for some N """ from sage.groups.matrix_gps.matrix_group import is_MatrixGroup if is_MatrixGroup(args[0]): G = args[0] elif type(args[0]) == type([]): G = MatrixGroup(args[0]) elif args[0] in ZZ: M = MatrixSpace(Zmod(args[0]), 2) G = MatrixGroup([M(x) for x in args[1]]) R = G.matrix_space().base_ring() if not hasattr(R, "cover_ring") or R.cover_ring() != ZZ: raise TypeError, "Ring of definition must be Z / NZ for some N" if not all([x.matrix().det() == 1 for x in G.gens()]): raise ValueError, "Group must be contained in SL(2, Z / N)" GG = _minimize_level(G) if GG in ZZ: from all import Gamma return Gamma(GG) else: return CongruenceSubgroupFromGroup(GG)
def CongruenceSubgroup_constructor(*args): r""" Attempt to create a congruence subgroup from the given data. The allowed inputs are as follows: - A :class:`~sage.groups.matrix_gps.matrix_group.MatrixGroup` object. This must be a group of matrices over `\ZZ / N\ZZ` for some `N`, with determinant 1, in which case the function will return the group of matrices in `SL(2, \ZZ)` whose reduction mod `N` is in the given group. - A list of matrices over `\ZZ / N\ZZ` for some `N`. The function will then compute the subgroup of `SL(2, \ZZ)` generated by these matrices, and proceed as above. - An integer `N` and a list of matrices (over any ring coercible to `\ZZ / N\ZZ`, e.g. over `\ZZ`). The matrices will then be coerced to `\ZZ / N\ZZ`. The function checks that the input G is valid. It then tests to see if `G` is the preimage mod `N` of some group of matrices modulo a proper divisor `M` of `N`, in which case it replaces `G` with this group before continuing. EXAMPLES:: sage: from sage.modular.arithgroup.congroup_generic import CongruenceSubgroup_constructor as CS sage: CS(2, [[1,1,0,1]]) Congruence subgroup of SL(2,Z) of level 2, preimage of: Matrix group over Ring of integers modulo 2 with 1 generators ( [1 1] [0 1] ) sage: CS([matrix(Zmod(2), 2, [1,1,0,1])]) Congruence subgroup of SL(2,Z) of level 2, preimage of: Matrix group over Ring of integers modulo 2 with 1 generators ( [1 1] [0 1] ) sage: CS(MatrixGroup([matrix(Zmod(2), 2, [1,1,0,1])])) Congruence subgroup of SL(2,Z) of level 2, preimage of: Matrix group over Ring of integers modulo 2 with 1 generators ( [1 1] [0 1] ) sage: CS(SL(2, 2)) Modular Group SL(2,Z) Some invalid inputs:: sage: CS(SU(2, 7)) Traceback (most recent call last): ... TypeError: Ring of definition must be Z / NZ for some N """ from sage.groups.matrix_gps.matrix_group import is_MatrixGroup if is_MatrixGroup(args[0]): G = args[0] elif isinstance(args[0], list): G = MatrixGroup(args[0]) elif args[0] in ZZ: M = MatrixSpace(Zmod(args[0]), 2) G = MatrixGroup([M(x) for x in args[1]]) R = G.matrix_space().base_ring() if not hasattr(R, "cover_ring") or R.cover_ring() != ZZ: raise TypeError("Ring of definition must be Z / NZ for some N") if not all([x.matrix().det() == 1 for x in G.gens()]): raise ValueError("Group must be contained in SL(2, Z / N)") GG = _minimize_level(G) if GG in ZZ: from .all import Gamma return Gamma(GG) else: return CongruenceSubgroupFromGroup(GG)
def QuaternionMatrixGroupGF3(): r""" The quaternion group as a set of `2\times 2` matrices over `GF(3)`. OUTPUT: A matrix group consisting of `2\times 2` matrices with elements from the finite field of order 3. The group is the quaternion group, the nonabelian group of order 8 that is not isomorphic to the group of symmetries of a square (the dihedral group `D_4`). .. note:: This group is most easily available via ``groups.matrix.QuaternionGF3()``. EXAMPLES: The generators are the matrix representations of the elements commonly called `I` and `J`, while `K` is the product of `I` and `J`. :: sage: from sage.groups.misc_gps.misc_groups import QuaternionMatrixGroupGF3 sage: Q = QuaternionMatrixGroupGF3() sage: Q.order() 8 sage: aye = Q.gens()[0]; aye [1 1] [1 2] sage: jay = Q.gens()[1]; jay [2 1] [1 1] sage: kay = aye*jay; kay [0 2] [1 0] TESTS:: sage: groups.matrix.QuaternionGF3() 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()) True sage: H = DihedralGroup(4) sage: H.order() 8 sage: QP.is_abelian(), H.is_abelian() (False, False) sage: QP.is_isomorphic(H) False """ from sage.rings.finite_rings.constructor import FiniteField from sage.matrix.matrix_space import MatrixSpace from sage.groups.matrix_gps.all import MatrixGroup MS = MatrixSpace(FiniteField(3), 2) aye = MS([1, 1, 1, 2]) jay = MS([2, 1, 1, 1]) return MatrixGroup([aye, jay])