def GammaH_constructor(level, H): r""" Return the congruence subgroup `\Gamma_H(N)`, which is the subgroup of `SL_2(\ZZ)` consisting of matrices of the form `\begin{pmatrix} a & b \\ c & d \end{pmatrix}` with `N | c` and `a, b \in H`, for `H` a specified subgroup of `(\ZZ/N\ZZ)^\times`. INPUT: - level -- an integer - H -- either 0, 1, or a list * If H is a list, return `\Gamma_H(N)`, where `H` is the subgroup of `(\ZZ/N\ZZ)^*` **generated** by the elements of the list. * If H = 0, returns `\Gamma_0(N)`. * If H = 1, returns `\Gamma_1(N)`. EXAMPLES:: sage: GammaH(11,0) # indirect doctest Congruence Subgroup Gamma0(11) sage: GammaH(11,1) Congruence Subgroup Gamma1(11) sage: GammaH(11,[10]) Congruence Subgroup Gamma_H(11) with H generated by [10] sage: GammaH(11,[10,1]) Congruence Subgroup Gamma_H(11) with H generated by [10] sage: GammaH(14,[10]) Traceback (most recent call last): ... ArithmeticError: The generators [10] must be units modulo 14 """ from all import Gamma0, Gamma1, SL2Z if level == 1: return SL2Z elif H == 0: return Gamma0(level) elif H == 1: return Gamma1(level) H = _normalize_H(H, level) if H == []: return Gamma1(level) Hlist = _list_subgroup(level, H) if len(Hlist) == euler_phi(level): return Gamma0(level) key = (level, tuple(H)) try: return _gammaH_cache[key] except KeyError: _gammaH_cache[key] = GammaH_class(level, H, Hlist) return _gammaH_cache[key]
def gamma0_coset_reps(self): r""" Return a set of coset representatives for self \\ Gamma0(N), where N is the level of self. EXAMPLE:: sage: GammaH(108, [1,-1]).gamma0_coset_reps() [ [1 0] [-43 -45] [ 31 33] [-49 -54] [ 25 28] [-19 -22] [0 1], [108 113], [108 115], [108 119], [108 121], [108 125], <BLANKLINE> [-17 -20] [ 47 57] [ 13 16] [ 41 52] [ 7 9] [-37 -49] [108 127], [108 131], [108 133], [108 137], [108 139], [108 143], <BLANKLINE> [-35 -47] [ 29 40] [ -5 -7] [ 23 33] [-11 -16] [ 53 79] [108 145], [108 149], [108 151], [108 155], [108 157], [108 161] ] """ from all import Gamma0 N = self.level() G = Gamma0(N) s = [] return [ G(lift_to_sl2z(0, d.lift(), N)) for d in _GammaH_coset_helper(N, self._list_of_elements_in_H()) ]
def coset_reps(self): r""" Return a set of coset representatives for self \\ SL2Z. EXAMPLES:: sage: list(Gamma1(3).coset_reps()) [[1 0] [0 1], [-1 -2] [ 3 5], [ 0 -1] [ 1 0], [-2 1] [ 5 -3], [1 0] [1 1], [-3 -2] [ 8 5], [ 0 -1] [ 1 2], [-2 -3] [ 5 7]] sage: len(list(Gamma1(31).coset_reps())) == 31**2 - 1 True """ from all import Gamma0, SL2Z reps1 = Gamma0(self.level()).coset_reps() for r in reps1: reps2 = self.gamma0_coset_reps() for t in reps2: yield SL2Z(t) * r
def nu2(self): r""" Return the number of orbits of elliptic points of order 2 for this arithmetic subgroup. EXAMPLES:: sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup().nu2() Traceback (most recent call last): ... NotImplementedError sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup.nu2(Gamma0(1105)) == 8 True """ # Subgroups not containing -1 have no elliptic points of order 2. if not self.is_even(): return 0 # Cheap trick: if self is a subgroup of something with no elliptic points, # then self has no elliptic points either. from all import Gamma0, is_CongruenceSubgroup if is_CongruenceSubgroup(self): if self.is_subgroup(Gamma0(self.level())) and Gamma0( self.level()).nu2() == 0: return 0 # Otherwise, the number of elliptic points is the number of g in self \ # SL2Z such that the stabiliser of g * i in self is not trivial. (Note # that the points g*i for g in the coset reps are not distinct, but it # still works, since the failure of these points to be distinct happens # precisely when the preimages are not elliptic.) from all import SL2Z count = 0 for g in self.coset_reps(): if g * SL2Z([0, 1, -1, 0]) * (~g) in self: count += 1 return count
def _new_group_from_level(self, level): r""" Return a new group of the same type (Gamma0, Gamma1, or GammaH) as self of the given level. In the case that self is of type GammaH, we take the largest H inside `(\ZZ/ \text{level}\ZZ)^\times` which maps to H, namely its inverse image under the natural reduction map. EXAMPLES:: sage: G = Gamma0(20) sage: G._new_group_from_level(4) Congruence Subgroup Gamma0(4) sage: G._new_group_from_level(40) Congruence Subgroup Gamma0(40) sage: G = Gamma1(10) sage: G._new_group_from_level(6) Traceback (most recent call last): ... ValueError: one level must divide the other sage: G = GammaH(50,[7]); G Congruence Subgroup Gamma_H(50) with H generated by [7] sage: G._new_group_from_level(25) Congruence Subgroup Gamma_H(25) with H generated by [7] sage: G._new_group_from_level(10) Congruence Subgroup Gamma0(10) sage: G._new_group_from_level(100) Congruence Subgroup Gamma_H(100) with H generated by [7, 57] """ from congroup_gamma0 import is_Gamma0 from congroup_gamma1 import is_Gamma1 from congroup_gammaH import is_GammaH from all import Gamma0, Gamma1, GammaH N = self.level() if (level % N) and (N % level): raise ValueError, "one level must divide the other" if is_Gamma0(self): return Gamma0(level) elif is_Gamma1(self): return Gamma1(level) elif is_GammaH(self): H = self._generators_for_H() if level > N: d = level // N diffs = [N * i for i in range(d)] newH = [h + diff for h in H for diff in diffs] return GammaH(level, [x for x in newH if gcd(level, x) == 1]) else: return GammaH(level, [h % level for h in H]) else: raise NotImplementedError
def nu3(self): r""" Return the number of orbits of elliptic points of order 3 for this arithmetic subgroup. EXAMPLES:: sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup().nu3() Traceback (most recent call last): ... NotImplementedError sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup.nu3(Gamma0(1729)) == 8 True We test that a bug in handling of subgroups not containing -1 is fixed: :: sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup.nu3(GammaH(7, [2])) 2 """ # Cheap trick: if self is a subgroup of something with no elliptic points, # then self has no elliptic points either. from all import Gamma0, is_CongruenceSubgroup if is_CongruenceSubgroup(self): if self.is_subgroup(Gamma0(self.level())) and Gamma0( self.level()).nu3() == 0: return 0 from all import SL2Z count = 0 for g in self.coset_reps(): if g * SL2Z([0, 1, -1, -1]) * (~g) in self: count += 1 if self.is_even(): return count else: return count / 2
def dimension_new_cusp_forms(self, k=2, eps=None, p=0, algorithm="CohenOesterle"): r""" Dimension of the new subspace (or `p`-new subspace) of cusp forms of weight `k` and character `\varepsilon`. INPUT: - ``k`` - an integer (default: 2) - ``eps`` - a Dirichlet character - ``p`` - a prime (default: 0); just the `p`-new subspace if given - ``algorithm`` - either "CohenOesterle" (the default) or "Quer". This specifies the method to use in the case of nontrivial character: either the Cohen--Oesterle formula as described in Stein's book, or by Moebius inversion using the subgroups GammaH (a method due to Jordi Quer). EXAMPLES:: sage: G = DirichletGroup(9) sage: eps = G.0^3 sage: eps.conductor() 3 sage: [Gamma1(9).dimension_new_cusp_forms(k, eps) for k in [2..10]] [0, 0, 0, 2, 0, 2, 0, 2, 0] sage: [Gamma1(9).dimension_cusp_forms(k, eps) for k in [2..10]] [0, 0, 0, 2, 0, 4, 0, 6, 0] sage: [Gamma1(9).dimension_new_cusp_forms(k, eps, 3) for k in [2..10]] [0, 0, 0, 2, 0, 2, 0, 2, 0] Double check using modular symbols (independent calculation):: sage: [ModularSymbols(eps,k,sign=1).cuspidal_subspace().new_subspace().dimension() for k in [2..10]] [0, 0, 0, 2, 0, 2, 0, 2, 0] sage: [ModularSymbols(eps,k,sign=1).cuspidal_subspace().new_subspace(3).dimension() for k in [2..10]] [0, 0, 0, 2, 0, 2, 0, 2, 0] Another example at level 33:: sage: G = DirichletGroup(33) sage: eps = G.1 sage: eps.conductor() 11 sage: [Gamma1(33).dimension_new_cusp_forms(k, G.1) for k in [2..4]] [0, 4, 0] sage: [Gamma1(33).dimension_new_cusp_forms(k, G.1, algorithm="Quer") for k in [2..4]] [0, 4, 0] sage: [Gamma1(33).dimension_new_cusp_forms(k, G.1^2) for k in [2..4]] [2, 0, 6] sage: [Gamma1(33).dimension_new_cusp_forms(k, G.1^2, p=3) for k in [2..4]] [2, 0, 6] """ if eps == None: return GammaH_class.dimension_new_cusp_forms(self, k, p) N = self.level() eps = DirichletGroup(N)(eps) from all import Gamma0 if eps.is_trivial(): return Gamma0(N).dimension_new_cusp_forms(k, p) from congroup_gammaH import mumu if p == 0 or N % p != 0 or eps.conductor().valuation(p) == N.valuation( p): D = [eps.conductor() * d for d in divisors(N // eps.conductor())] return sum([ Gamma1_constructor(M).dimension_cusp_forms( k, eps.restrict(M), algorithm) * mumu(N // M) for M in D ]) eps_p = eps.restrict(N // p) old = Gamma1_constructor(N // p).dimension_cusp_forms( k, eps_p, algorithm) return self.dimension_cusp_forms(k, eps, algorithm) - 2 * old
def dimension_eis(self, k=2, eps=None, algorithm="CohenOesterle"): r""" Return the dimension of the space of Eisenstein series forms for self, or the dimension of the subspace corresponding to the given character if one is supplied. INPUT: - ``k`` - an integer (default: 2), the weight. - ``eps`` - either None or a Dirichlet character modulo N, where N is the level of this group. If this is None, then the dimension of the whole space is returned; otherwise, the dimension of the subspace of Eisenstein series of character eps. - ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This specifies the method to use in the case of nontrivial character: either the Cohen--Oesterle formula as described in Stein's book, or by Moebius inversion using the subgroups GammaH (a method due to Jordi Quer). AUTHORS: - William Stein - Cohen--Oesterle algorithm - Jordi Quer - algorithm based on GammaH subgroups - David Loeffler (2009) - code refactoring EXAMPLES: The following two computations use different algorithms: :: sage: [Gamma1(36).dimension_eis(1,eps) for eps in DirichletGroup(36)] [0, 4, 3, 0, 0, 2, 6, 0, 0, 2, 3, 0] sage: [Gamma1(36).dimension_eis(1,eps,algorithm="Quer") for eps in DirichletGroup(36)] [0, 4, 3, 0, 0, 2, 6, 0, 0, 2, 3, 0] So do these: :: sage: [Gamma1(48).dimension_eis(3,eps) for eps in DirichletGroup(48)] [0, 12, 0, 4, 0, 8, 0, 4, 12, 0, 4, 0, 8, 0, 4, 0] sage: [Gamma1(48).dimension_eis(3,eps,algorithm="Quer") for eps in DirichletGroup(48)] [0, 12, 0, 4, 0, 8, 0, 4, 12, 0, 4, 0, 8, 0, 4, 0] """ from all import Gamma0 # first deal with special cases if eps is None: return GammaH_class.dimension_eis(self, k) N = self.level() eps = DirichletGroup(N)(eps) if eps.is_trivial(): return Gamma0(N).dimension_eis(k) # Note case of k = 0 and trivial character already dealt with separately, so k <= 0 here is valid: if (k <= 0) or ((k % 2) == 1 and eps.is_even()) or ((k % 2) == 0 and eps.is_odd()): return ZZ(0) if algorithm == "Quer": n = eps.order() dim = ZZ(0) for d in n.divisors(): G = GammaH_constructor(N, (eps**d).kernel()) dim = dim + moebius(d) * G.dimension_eis(k) return dim // phi(n) elif algorithm == "CohenOesterle": from sage.modular.dims import CohenOesterle K = eps.base_ring() j = 2 - k # We use the Cohen-Oesterle formula in a subtle way to # compute dim M_k(N,eps) (see Ch. 6 of William Stein's book on # computing with modular forms). alpha = -ZZ( K(Gamma0(N).index() * (j - 1) / ZZ(12)) + CohenOesterle(eps, j)) if k == 1: return alpha else: return alpha - self.dimension_cusp_forms(k, eps) else: #algorithm not in ["CohenOesterle", "Quer"]: raise ValueError, "Unrecognised algorithm in dimension_eis"
def dimension_cusp_forms(self, k=2, eps=None, algorithm="CohenOesterle"): r""" Return the dimension of the space of cusp forms for self, or the dimension of the subspace corresponding to the given character if one is supplied. INPUT: - ``k`` - an integer (default: 2), the weight. - ``eps`` - either None or a Dirichlet character modulo N, where N is the level of this group. If this is None, then the dimension of the whole space is returned; otherwise, the dimension of the subspace of forms of character eps. - ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This specifies the method to use in the case of nontrivial character: either the Cohen--Oesterle formula as described in Stein's book, or by Moebius inversion using the subgroups GammaH (a method due to Jordi Quer). EXAMPLES: We compute the same dimension in two different ways :: sage: K = CyclotomicField(3) sage: eps = DirichletGroup(7*43,K).0^2 sage: G = Gamma1(7*43) Via Cohen--Oesterle: :: sage: Gamma1(7*43).dimension_cusp_forms(2, eps) 28 Via Quer's method: :: sage: Gamma1(7*43).dimension_cusp_forms(2, eps, algorithm="Quer") 28 Some more examples: :: sage: G.<eps> = DirichletGroup(9) sage: [Gamma1(9).dimension_cusp_forms(k, eps) for k in [1..10]] [0, 0, 1, 0, 3, 0, 5, 0, 7, 0] sage: [Gamma1(9).dimension_cusp_forms(k, eps^2) for k in [1..10]] [0, 0, 0, 2, 0, 4, 0, 6, 0, 8] """ from all import Gamma0 # first deal with special cases if eps is None: return GammaH_class.dimension_cusp_forms(self, k) N = self.level() if eps.base_ring().characteristic() != 0: raise ValueError eps = DirichletGroup(N, eps.base_ring())(eps) if eps.is_trivial(): return Gamma0(N).dimension_cusp_forms(k) if (k <= 0) or ((k % 2) == 1 and eps.is_even()) or ((k % 2) == 0 and eps.is_odd()): return ZZ(0) if k == 1: try: n = self.dimension_cusp_forms(1) if n == 0: return ZZ(0) else: # never happens at present raise NotImplementedError, "Computations of dimensions of spaces of weight 1 cusp forms not implemented at present" except NotImplementedError: raise # now the main part if algorithm == "Quer": n = eps.order() dim = ZZ(0) for d in n.divisors(): G = GammaH_constructor(N, (eps**d).kernel()) dim = dim + moebius(d) * G.dimension_cusp_forms(k) return dim // phi(n) elif algorithm == "CohenOesterle": K = eps.base_ring() from sage.modular.dims import CohenOesterle from all import Gamma0 return ZZ( K(Gamma0(N).index() * (k - 1) / ZZ(12)) + CohenOesterle(eps, k)) else: #algorithm not in ["CohenOesterle", "Quer"]: raise ValueError, "Unrecognised algorithm in dimension_cusp_forms"