def multiple_of_order(self, maxp=None, proof=True): """ Return a multiple of the order. INPUT: - ``proof`` -- a boolean (default: True) The computation of the rational torsion order of J1(p) is conjectural and will only be used if proof=False. See Section 6.2.3 of [CES2003]_. EXAMPLES:: sage: J = J1(11); J Abelian variety J1(11) of dimension 1 sage: J.rational_torsion_subgroup().multiple_of_order() 5 sage: J = J0(17) sage: J.rational_torsion_subgroup().order() 4 This is an example where proof=False leads to a better bound and better performance. :: sage: J = J1(23) sage: J.rational_torsion_subgroup().multiple_of_order() # long time (2s) 9406793 sage: J.rational_torsion_subgroup().multiple_of_order(proof=False) 408991 """ try: if proof: return self._multiple_of_order else: return self._multiple_of_order_proof_false except AttributeError: pass A = self.abelian_variety() N = A.level() if A.dimension() == 0: self._multiple_of_order = ZZ(1) self._multiple_of_order_proof_false = self._multiple_of_order return self._multiple_of_order # return the order of the cuspidal subgroup in the J0(p) case if A.is_J0() and N.is_prime(): self._multiple_of_order = QQ((A.level() - 1) / 12).numerator() self._multiple_of_order_proof_false = self._multiple_of_order return self._multiple_of_order # The elliptic curve case if A.dimension() == 1: self._multiple_of_order = A.elliptic_curve().torsion_order() self._multiple_of_order_proof_false = self._multiple_of_order return self._multiple_of_order # The conjectural J1(p) case if not proof and A.is_J1() and N.is_prime(): epsilons = [ epsilon for epsilon in DirichletGroup(N) if not epsilon.is_trivial() and epsilon.is_even() ] bernoullis = [epsilon.bernoulli(2) for epsilon in epsilons] self._multiple_of_order_proof_false = ZZ(N / (2**(N - 3)) * prod(bernoullis)) return self._multiple_of_order_proof_false # The Gamma0 and Gamma1 case if all((is_Gamma0(G) or is_Gamma1(G) for G in A.groups())): self._multiple_of_order = self.multiple_of_order_using_frobp() return self._multiple_of_order # Unhandled case raise NotImplementedError("No implemented algorithm")
def possible_orders(self, proof=True): """ Return the possible orders of this torsion subgroup. Outside of special cases, this is done by computing a divisor and multiple of the order. INPUT: - ``proof`` -- a boolean (default: True) OUTPUT: - an array of positive integers The computation of the rational torsion order of J1(p) is conjectural and will only be used if proof=False. See Section 6.2.3 of [CES2003]_. EXAMPLES:: sage: J0(11).rational_torsion_subgroup().possible_orders() [5] sage: J0(33).rational_torsion_subgroup().possible_orders() [100, 200] sage: J1(13).rational_torsion_subgroup().possible_orders() [19] sage: J1(16).rational_torsion_subgroup().possible_orders() [1, 2, 4, 5, 10, 20] """ try: if proof: return self._possible_orders else: return self._possible_orders_proof_false except AttributeError: pass A = self.abelian_variety() N = A.level() # return the order of the cuspidal subgroup in the J0(p) case if A.is_J0() and N.is_prime(): self._possible_orders = [QQ((A.level() - 1) / 12).numerator()] self._possible_orders_proof_false = self._possible_orders return self._possible_orders # the elliptic curve case if A.dimension() == 1: self._possible_orders = [A.elliptic_curve().torsion_order()] self._possible_orders_proof_false = self._possible_orders return self._possible_orders # the conjectural J1(p) case if not proof and A.is_J1() and N.is_prime(): epsilons = [ epsilon for epsilon in DirichletGroup(N) if not epsilon.is_trivial() and epsilon.is_even() ] bernoullis = [epsilon.bernoulli(2) for epsilon in epsilons] self._possible_orders_proof_false = [ ZZ(N / (2**(N - 3)) * prod(bernoullis)) ] return self._possible_orders_proof_false u = self.multiple_of_order() l = self.divisor_of_order() assert u % l == 0 O = [l * d for d in divisors(u // l)] self._possible_orders = O if u == l: self._possible_orders_proof_false = O return O
def divisor_of_order(self): """ Return a divisor of the order of this torsion subgroup of a modular abelian variety. OUTPUT: A divisor of this torsion subgroup. EXAMPLES:: sage: t = J0(37)[1].rational_torsion_subgroup() sage: t.divisor_of_order() 3 sage: J = J1(19) sage: J.rational_torsion_subgroup().divisor_of_order() 4383 sage: J = J0(45) sage: J.rational_cusp_subgroup().order() 32 sage: J.rational_cuspidal_subgroup().order() 64 sage: J.rational_torsion_subgroup().divisor_of_order() 64 """ try: return self._divisor_of_order except AttributeError: pass A = self.abelian_variety() N = A.level() if A.dimension() == 0: self._divisor_of_order = ZZ(1) return self._divisor_of_order # return the order of the cuspidal subgroup in the J0(p) case if A.is_J0() and N.is_prime(): self._divisor_of_order = QQ((A.level() - 1) / 12).numerator() return self._divisor_of_order # The elliptic curve case if A.dimension() == 1: self._divisor_of_order = A.elliptic_curve().torsion_order() return self._divisor_of_order # The J1(p) case if A.is_J1() and N.is_prime(): epsilons = [ epsilon for epsilon in DirichletGroup(N) if not epsilon.is_trivial() and epsilon.is_even() ] bernoullis = [epsilon.bernoulli(2) for epsilon in epsilons] self._divisor_of_order = ZZ(N / (2**(N - 3)) * prod(bernoullis)) return self._divisor_of_order # The Gamma0 case if all(is_Gamma0(G) for G in A.groups()): self._divisor_of_order = A.rational_cuspidal_subgroup().order() return self._divisor_of_order # Unhandled case self._divisor_of_order = ZZ(1) return self._divisor_of_order
def hecke_operator_on_basis(B, n, k, eps=None, already_echelonized=False): r""" Given a basis `B` of `q`-expansions for a space of modular forms with character `\varepsilon` to precision at least `\#B\cdot n+1`, this function computes the matrix of `T_n` relative to `B`. .. note:: If the elements of B are not known to sufficient precision, this function will report that the vectors are linearly dependent (since they are to the specified precision). INPUT: - ``B`` - list of q-expansions - ``n`` - an integer >= 1 - ``k`` - an integer - ``eps`` - Dirichlet character - ``already_echelonized`` -- bool (default: False); if True, use that the basis is already in Echelon form, which saves a lot of time. EXAMPLES:: sage: sage.modular.modform.constructor.ModularForms_clear_cache() sage: ModularForms(1,12).q_expansion_basis() [ q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 + O(q^6), 1 + 65520/691*q + 134250480/691*q^2 + 11606736960/691*q^3 + 274945048560/691*q^4 + 3199218815520/691*q^5 + O(q^6) ] sage: hecke_operator_on_basis(ModularForms(1,12).q_expansion_basis(), 3, 12) Traceback (most recent call last): ... ValueError: The given basis vectors must be linearly independent. sage: hecke_operator_on_basis(ModularForms(1,12).q_expansion_basis(30), 3, 12) [ 252 0] [ 0 177148] TESTS: This shows that the problem with finite fields reported at :trac:`8281` is solved:: sage: bas_mod5 = [f.change_ring(GF(5)) for f in victor_miller_basis(12, 20)] sage: hecke_operator_on_basis(bas_mod5, 2, 12) [4 0] [0 1] This shows that empty input is handled sensibly (:trac:`12202`):: sage: x = hecke_operator_on_basis([], 3, 12); x [] sage: x.parent() Full MatrixSpace of 0 by 0 dense matrices over Cyclotomic Field of order 1 and degree 1 sage: y = hecke_operator_on_basis([], 3, 12, eps=DirichletGroup(13).0^2); y [] sage: y.parent() Full MatrixSpace of 0 by 0 dense matrices over Cyclotomic Field of order 12 and degree 4 """ if not isinstance(B, (list, tuple)): raise TypeError("B (=%s) must be a list or tuple" % B) if len(B) == 0: if eps is None: R = CyclotomicField(1) else: R = eps.base_ring() return MatrixSpace(R, 0)(0) f = B[0] R = f.base_ring() if eps is None: eps = DirichletGroup(1, R)[0] all_powerseries = True for x in B: if not is_PowerSeries(x): all_powerseries = False if not all_powerseries: raise TypeError("each element of B must be a power series") n = Integer(n) k = Integer(k) prec = (f.prec() - 1) // n A = R**prec V = A.span_of_basis([g.padded_list(prec) for g in B], already_echelonized=already_echelonized) return _hecke_operator_on_basis(B, V, n, k, eps)
def hecke_operator_on_qexp(f, n, k, eps=None, prec=None, check=True, _return_list=False): r""" Given the `q`-expansion `f` of a modular form with character `\varepsilon`, this function computes the image of `f` under the Hecke operator `T_{n,k}` of weight `k`. EXAMPLES:: sage: M = ModularForms(1,12) sage: hecke_operator_on_qexp(M.basis()[0], 3, 12) 252*q - 6048*q^2 + 63504*q^3 - 370944*q^4 + O(q^5) sage: hecke_operator_on_qexp(M.basis()[0], 1, 12, prec=7) q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 - 6048*q^6 + O(q^7) sage: hecke_operator_on_qexp(M.basis()[0], 1, 12) q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 - 6048*q^6 - 16744*q^7 + 84480*q^8 - 113643*q^9 - 115920*q^10 + 534612*q^11 - 370944*q^12 - 577738*q^13 + O(q^14) sage: M.prec(20) 20 sage: hecke_operator_on_qexp(M.basis()[0], 3, 12) 252*q - 6048*q^2 + 63504*q^3 - 370944*q^4 + 1217160*q^5 - 1524096*q^6 + O(q^7) sage: hecke_operator_on_qexp(M.basis()[0], 1, 12) q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 - 6048*q^6 - 16744*q^7 + 84480*q^8 - 113643*q^9 - 115920*q^10 + 534612*q^11 - 370944*q^12 - 577738*q^13 + 401856*q^14 + 1217160*q^15 + 987136*q^16 - 6905934*q^17 + 2727432*q^18 + 10661420*q^19 - 7109760*q^20 + O(q^21) sage: (hecke_operator_on_qexp(M.basis()[0], 1, 12)*252).add_bigoh(7) 252*q - 6048*q^2 + 63504*q^3 - 370944*q^4 + 1217160*q^5 - 1524096*q^6 + O(q^7) sage: hecke_operator_on_qexp(M.basis()[0], 6, 12) -6048*q + 145152*q^2 - 1524096*q^3 + O(q^4) An example on a formal power series:: sage: R.<q> = QQ[[]] sage: f = q + q^2 + q^3 + q^7 + O(q^8) sage: hecke_operator_on_qexp(f, 3, 12) q + O(q^3) sage: hecke_operator_on_qexp(delta_qexp(24), 3, 12).prec() 8 sage: hecke_operator_on_qexp(delta_qexp(25), 3, 12).prec() 9 An example of computing `T_{p,k}` in characteristic `p`:: sage: p = 199 sage: fp = delta_qexp(prec=p^2+1, K=GF(p)) sage: tfp = hecke_operator_on_qexp(fp, p, 12) sage: tfp == fp[p] * fp True sage: tf = hecke_operator_on_qexp(delta_qexp(prec=p^2+1), p, 12).change_ring(GF(p)) sage: tfp == tf True """ if eps is None: # Need to have base_ring=ZZ to work over finite fields, since # ZZ can coerce to GF(p), but QQ can't. eps = DirichletGroup(1, base_ring=ZZ)[0] if check: if not (is_PowerSeries(f) or is_ModularFormElement(f)): raise TypeError("f (=%s) must be a power series or modular form" % f) if not is_DirichletCharacter(eps): raise TypeError("eps (=%s) must be a Dirichlet character" % eps) k = Integer(k) n = Integer(n) v = [] if prec is None: if is_ModularFormElement(f): # always want at least three coefficients, but not too many, unless # requested pr = max(f.prec(), f.parent().prec(), (n + 1) * 3) pr = min(pr, 100 * (n + 1)) prec = pr // n + 1 else: prec = (f.prec() / ZZ(n)).ceil() if prec == Infinity: prec = f.parent().default_prec() // n + 1 if f.prec() < prec: f._compute_q_expansion(prec) p = Integer(f.base_ring().characteristic()) if k != 1 and p.is_prime() and n.is_power_of(p): # if computing T_{p^a} in characteristic p, use the simpler (and faster) # formula v = [f[m * n] for m in range(prec)] else: l = k - 1 for m in range(prec): am = sum([eps(d) * d**l * f[m*n//(d*d)] for \ d in divisors(gcd(n, m)) if (m*n) % (d*d) == 0]) v.append(am) if _return_list: return v if is_ModularFormElement(f): R = f.parent()._q_expansion_ring() else: R = f.parent() return R(v, prec)
def half_integral_weight_modform_basis(chi, k, prec): r""" A basis for the space of weight `k/2` forms with character `\chi`. The modulus of `\chi` must be divisible by `16` and `k` must be odd and `>1`. INPUT: - ``chi`` - a Dirichlet character with modulus divisible by 16 - ``k`` - an odd integer = 1 - ``prec`` - a positive integer OUTPUT: a list of power series .. warning:: 1. This code is very slow because it requests computation of a basis of modular forms for integral weight spaces, and that computation is still very slow. 2. If you give an input prec that is too small, then the output list of power series may be larger than the dimension of the space of half-integral forms. EXAMPLES: We compute some half-integral weight forms of level 16\*7 :: sage: half_integral_weight_modform_basis(DirichletGroup(16*7).0^2,3,30) [q - 2*q^2 - q^9 + 2*q^14 + 6*q^18 - 2*q^21 - 4*q^22 - q^25 + O(q^30), q^2 - q^14 - 3*q^18 + 2*q^22 + O(q^30), q^4 - q^8 - q^16 + q^28 + O(q^30), q^7 - 2*q^15 + O(q^30)] The following illustrates that choosing too low of a precision can give an incorrect answer. :: sage: half_integral_weight_modform_basis(DirichletGroup(16*7).0^2,3,20) [q - 2*q^2 - q^9 + 2*q^14 + 6*q^18 + O(q^20), q^2 - q^14 - 3*q^18 + O(q^20), q^4 - 2*q^8 + 2*q^12 - 4*q^16 + O(q^20), q^7 - 2*q^8 + 4*q^12 - 2*q^15 - 6*q^16 + O(q^20), q^8 - 2*q^12 + 3*q^16 + O(q^20)] We compute some spaces of low level and the first few possible weights. :: sage: half_integral_weight_modform_basis(DirichletGroup(16,QQ).1, 3, 10) [] sage: half_integral_weight_modform_basis(DirichletGroup(16,QQ).1, 5, 10) [q - 2*q^3 - 2*q^5 + 4*q^7 - q^9 + O(q^10)] sage: half_integral_weight_modform_basis(DirichletGroup(16,QQ).1, 7, 10) [q - 2*q^2 + 4*q^3 + 4*q^4 - 10*q^5 - 16*q^7 + 19*q^9 + O(q^10), q^2 - 2*q^3 - 2*q^4 + 4*q^5 + 4*q^7 - 8*q^9 + O(q^10), q^3 - 2*q^5 - 2*q^7 + 4*q^9 + O(q^10)] sage: half_integral_weight_modform_basis(DirichletGroup(16,QQ).1, 9, 10) [q - 2*q^2 + 4*q^3 - 8*q^4 + 14*q^5 + 16*q^6 - 40*q^7 + 16*q^8 - 57*q^9 + O(q^10), q^2 - 2*q^3 + 4*q^4 - 8*q^5 - 8*q^6 + 20*q^7 - 8*q^8 + 32*q^9 + O(q^10), q^3 - 2*q^4 + 4*q^5 + 4*q^6 - 10*q^7 - 16*q^9 + O(q^10), q^4 - 2*q^5 - 2*q^6 + 4*q^7 + 4*q^9 + O(q^10), q^5 - 2*q^7 - 2*q^9 + O(q^10)] This example once raised an error (see trac #5792). :: sage: half_integral_weight_modform_basis(trivial_character(16),9,10) [q - 2*q^2 + 4*q^3 - 8*q^4 + 4*q^6 - 16*q^7 + 48*q^8 - 15*q^9 + O(q^10), q^2 - 2*q^3 + 4*q^4 - 2*q^6 + 8*q^7 - 24*q^8 + O(q^10), q^3 - 2*q^4 - 4*q^7 + 12*q^8 + O(q^10), q^4 - 6*q^8 + O(q^10)] ALGORITHM: Basmaji (page 55 of his Essen thesis, "Ein Algorithmus zur Berechnung von Hecke-Operatoren und Anwendungen auf modulare Kurven", http://wstein.org/scans/papers/basmaji/). Let `S = S_{k+1}(\epsilon)` be the space of cusp forms of even integer weight `k+1` and character `\varepsilon = \chi \psi^{(k+1)/2}`, where `\psi` is the nontrivial mod-4 Dirichlet character. Let `U` be the subspace of `S \times S` of elements `(a,b)` such that `\Theta_2 a = \Theta_3 b`. Then `U` is isomorphic to `S_{k/2}(\chi)` via the map `(a,b) \mapsto a/\Theta_3`. """ if chi.modulus() % 16: raise ValueError, "the character must have modulus divisible by 16" if not k % 2: raise ValueError, "k (=%s) must be odd" % k if k < 3: raise ValueError, "k (=%s) must be at least 3" % k chi = chi.minimize_base_ring() psi = chi.parent()(DirichletGroup(4, chi.base_ring()).gen()) eps = chi * psi**((k + 1) // 2) eps = eps.minimize_base_ring() M = constructor.ModularForms(eps, (k + 1) // 2) C = M.cuspidal_subspace() B = C.basis() # This computation of S below -- of course --dominates the whole function. #from sage.misc.all import cputime #tm = cputime() #print "Computing basis..." S = [f.q_expansion(prec) for f in B] #print "Time to compute basis", cputime(tm) T2 = theta2_qexp(prec) T3 = theta_qexp(prec) n = len(S) MS = MatrixSpace(M.base_ring(), 2 * n, prec) A = copy(MS.zero_matrix()) for i in range(n): T2f = T2 * S[i] T3f = T3 * S[i] for j in range(prec): A[i, j] = T2f[j] A[n + i, j] = -T3f[j] B = A.kernel().basis() a_vec = [sum([b[i] * S[i] for i in range(n)]) for b in B] if len(a_vec) == 0: return [] R = a_vec[0].parent() t3 = R(T3) return [R(a) / t3 for a in a_vec]
def dimension_of_ordinary_subspace(self, p=None, cusp=False): """ If ``cusp`` is ``True``, return dimension of cuspidal ordinary subspace. This does a weight 2 computation with sage's ModularSymbols. EXAMPLES:: sage: M = OverconvergentModularSymbols(11, 0, sign=-1, p=3, prec_cap=4, base=ZpCA(3, 8)) sage: M.dimension_of_ordinary_subspace() 2 sage: M.dimension_of_ordinary_subspace(cusp=True) 2 sage: M = OverconvergentModularSymbols(11, 0, sign=1, p=3, prec_cap=4, base=ZpCA(3, 8)) sage: M.dimension_of_ordinary_subspace(cusp=True) 2 sage: M.dimension_of_ordinary_subspace() 4 sage: M = OverconvergentModularSymbols(11, 0, sign=0, p=3, prec_cap=4, base=ZpCA(3, 8)) sage: M.dimension_of_ordinary_subspace() 6 sage: M.dimension_of_ordinary_subspace(cusp=True) 4 sage: M = OverconvergentModularSymbols(11, 0, sign=1, p=11, prec_cap=4, base=ZpCA(11, 8)) sage: M.dimension_of_ordinary_subspace(cusp=True) 1 sage: M.dimension_of_ordinary_subspace() 2 sage: M = OverconvergentModularSymbols(11, 2, sign=1, p=11, prec_cap=4, base=ZpCA(11, 8)) sage: M.dimension_of_ordinary_subspace(cusp=True) 0 sage: M.dimension_of_ordinary_subspace() 1 sage: M = OverconvergentModularSymbols(11, 10, sign=1, p=11, prec_cap=4, base=ZpCA(11, 8)) sage: M.dimension_of_ordinary_subspace(cusp=True) 1 sage: M.dimension_of_ordinary_subspace() 2 An example with odd weight and hence non-trivial character:: sage: K = Qp(11, 6) sage: DG = DirichletGroup(11, K) sage: chi = DG([K(378703)]) sage: MM = FamiliesOfOMS(chi, 1, p=11, prec_cap=[4, 4], base_coeffs=ZpCA(11, 4), sign=-1) sage: MM.dimension_of_ordinary_subspace() 1 """ try: p = self.prime() except AttributeError: if p is None: raise ValueError( "If self doesn't have a prime, must specify p.") try: return self._ord_dim_dict[(p, cusp)] except AttributeError: self._ord_dim_dict = {} except KeyError: pass from sage.modular.dirichlet import DirichletGroup from sage.rings.finite_rings.constructor import GF try: chi = self.character() except AttributeError: chi = DirichletGroup(self.level(), GF(p))[0] if chi is None: chi = DirichletGroup(self.level(), GF(p))[0] from sage.modular.modsym.modsym import ModularSymbols r = self.weight() % (p - 1) if chi.is_trivial(): N = chi.modulus() if N % p != 0: N *= p else: e = N.valuation(p) N.divide_knowing_divisible_by(p**(e - 1)) chi = DirichletGroup(N, GF(p))[0] elif chi.modulus() % p != 0: chi = DirichletGroup(chi.modulus() * p, GF(p))(chi) DG = DirichletGroup(chi.modulus(), GF(p)) if r == 0: from sage.modular.arithgroup.congroup_gamma0 import Gamma0_constructor as Gamma0 verbose("in dim: %s, %s, %s" % (self.sign(), chi, p)) M = ModularSymbols(DG(chi), 2, self.sign(), GF(p)) else: psi = [GF(p)(u)**r for u in DG.unit_gens()] #mod p Teichmuller^r psi = DG(psi) M = ModularSymbols(DG(chi) * psi, 2, self.sign(), GF(p)) if cusp: M = M.cuspidal_subspace() hecke_poly = M.hecke_polynomial(p) verbose("in dim: %s" % (hecke_poly)) x = hecke_poly.parent().gen() d = hecke_poly.degree() - hecke_poly.ord(x) self._ord_dim_dict[(p, cusp)] = d return d
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 Möbius 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() K = eps.base_ring() eps = DirichletGroup(N, K)(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 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_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 Möbius 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 is None: return GammaH_class.dimension_new_cusp_forms(self, k, p) N = self.level() eps = DirichletGroup(N, eps.base_ring())(eps) if eps.is_trivial(): from .all import Gamma0 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_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 Möbius 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() K = eps.base_ring() eps = DirichletGroup(N, K)(eps) if K.characteristic() != 0: raise NotImplementedError('dimension_cusp_forms() is only implemented for rings of characteristic 0') 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": from sage.modular.dims import CohenOesterle 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")
def make_table_of_spaces_fixed_level(level=1, character=0, weight_block=0, **kwds): r""" """ wlen = 15 print "make table: ", level, character, weight_block w_start = wlen * weight_block w_stop = wlen * (weight_block + 1) s = "<table><thead></thead><tbody>\n" s += "<tr><td>Weight \(k\):</td>" dims = dict() links = dict() character = int(character) x = trivial_character(level) if character > 0: D = DirichletGroup(level).list() x = D[int(character)] if x.is_even() and is_odd(w_start): w_start = w_start + 1 w_stop = w_stop + 1 if x.is_odd() and is_even(w_start): w_start = w_start + 1 w_stop = w_stop + 1 weights = list() for weight in range(w_start, w_start + 2 * wlen, 2): weights.append(weight) for weight in weights: s += "<td> %s </td>" % weight s += "</tr><tr>" if character > 0: s += "<td>Dimension:" # of \(S^{\\textrm{new}}_{k}(%s),\chi_{%s}\):" % (level,character) else: s += "<td>Dimension:" # of \(S^{\\textrm{new}}_{k}(%s)\):" % (level) for weight in weights: if character > 0: dims[weight] = dimension_new_cusp_forms(x, weight) else: dims[weight] = dimension_new_cusp_forms(level, weight) s += "<td> %s </td>" % dims[weight] j = 0 # we display ony even weight if the character is even print "w_start=", w_start print "w_stop=", w_stop for weight in weights: if weight not in dims: continue if dims[weight] > 0: url = url_for('emf.render_elliptic_modular_forms', level=level, weight=weight) if character > 0: lab = "\(S^{\\textrm{}}_{%s}(%s,\chi_{%s})\)" % (weight, level, character) else: # lab = " \(S_{%s}(%s)\)" %(weight,level) lab = " S<sup><small></small></sup><sub><small>%s</small></sub>(%s)" % ( weight, level) links[ weight] = "<a style=\"display:inline\" href=\"%s\">%s</a>" % ( url, lab) else: links[weight] = "" j += 1 if j >= wlen: exit l = max(map(len_as_printed, map(str, links))) * 10.0 s += "</tr><tr>" s += "<td>Link to space:</td>" for weight in weights: if weight in links: s += "<td width=\"%s\">%s</td>" % (l + 50, links[weight]) s += "</tr></tbody></table>" # print s return s
def set_table( info, is_set, make_link=True): # level_min,level_max,weight=2,chi=0,make_link=True): r""" make a bunch of html tables with information about spaces of modular forms with parameters in the given ranges. Should use database in the future... """ D = 0 rowlen = 10 # split into rows of this length... rowlen0 = rowlen rowlen1 = rowlen characters = dict() if ('level_min' in info): level_min = int(info['level_min']) else: level_min = 1 if ('level_max' in info): level_max = int(info['level_max']) else: level_max = 50 if (level_max - level_min + 1) < rowlen: rowlen0 = level_max - level_min + 1 if (info['list_chars'] != '0'): char1 = 1 else: char1 = 0 if (is_set['weight']): weight = int(info['weight']) else: weight = 2 ## setup the table # print "char11=",char1 tbl = dict() if (char1 == 1): tbl['header'] = 'Dimension of \( S_{' + str(weight) + '}(N,\chi_{n})\)' else: tbl['header'] = 'Dimension of \( S_{' + str(weight) + '}(N)\)' tbl['headersv'] = list() tbl['headersh'] = list() tbl['corner_label'] = "" tbl['data'] = list() tbl['data_format'] = 'html' tbl['class'] = "dimension_table" tbl['atts'] = "border=\"0\" class=\"data_table\"" num_rows = ceil(QQ(level_max - level_min + 1) / QQ(rowlen0)) print "num_rows=", num_rows for i in range(1, rowlen0 + 1): tbl['headersh'].append(i + level_min - 1) for r in range(num_rows): tbl['headersv'].append(r * rowlen0) print "level_min=", level_min print "level_max=", level_max print "char=", char1 for r in range(num_rows): row = list() for k in range(1, rowlen0 + 1): row.append("") # print "row nr. ",r for k in range(1, rowlen0 + 1): N = level_min - 1 + r * rowlen0 + k s = "<a name=\"#" + str(N) + "\"></a>" # print "col ",k,"=",N if (N > level_max or N < 1): continue if (char1 == 0): d = dimension_cusp_forms(N, weight) print "d=", d if (make_link): url = "?weight=" + str(weight) + "&level=" + str( N) + "&character=0" row.append(s + "<a target=\"mainWindow\" href=\"" + url + "\">" + str(d) + "</a>") else: row.append(s + str(d)) # print "dim(",N,weight,")=",d else: D = DirichletGroup(N) print "D=", D s = "<a name=\"#" + str(N) + "\"></a>" small_tbl = dict() # small_tbl['header']='Dimension of \( S_{'+str(weight)+'}(N)\)' small_tbl['headersv'] = ['\( d \)'] small_tbl['headersh'] = list() small_tbl['corner_label'] = "\( n \)" small_tbl['data'] = list() small_tbl['atts'] = "border=\"1\" padding=\"1\"" small_tbl['data_format'] = 'html' row1 = list() # num_small_rows = ceil(QQ(level_max) / QQ(rowlen)) ii = 0 for chi in range(0, len(D.list())): x = D[chi] S = CuspForms(x, weight) d = S.dimension() if (d == 0): continue small_tbl['headersh'].append(chi) if (make_link): url = "?weight=" + str(weight) + "&level=" + str( N) + "&character=" + str(chi) row1.append("<a target=\"mainWindow\" href=\"" + url + "\">" + str(d) + "</a>") else: row1.append(d) ii = ii + 1 print "d=", d if (ii > rowlen1 and len(row1) > 0): ## we make a new table since we may not have regularly dstributed labels # print "Break line! Make new table!" small_tbl['data'].append(row1) s = s + html_table(small_tbl) small_tbl['headersh'] = list() small_tbl['data'] = list() row1 = list() ii = 0 if (len(row1) > 0): small_tbl['data'].append(row1) if (len(row1) > 0 or len(small_tbl['data']) > 0): # print "small_tbl=",small_tbl ss = html_table(small_tbl) # print "ss=",ss s = s + ss # s=s+"\( \chi_{"+str(chi)+"}\) :"+str(d) # print N,k,chi,d # print s else: s = "All spaces are zero-dimensional!" row.append(s) print "row=", row tbl['data'].append(row) s = html_table(tbl) s = s + "\n <br> \(N=" + str(rowlen0) + "\cdot row+col\)" print "Whole table=", s ## ugly solution. but we have latex in the data fields... ss = re.sub('texttt', '', s) info['popup_table'] = ss # info['sidebar']=set_sidebar([navigation,parents,siblings,friends,lifts]) return info
def make_table_of_dimensions(level_start=1, level_stop=50, weight_start=1, weight_stop=24, char=0, **kwds): r""" make an html table with information about spaces of modular forms with parameters in the given ranges. using a fixed character. Should use database in the future... """ D = 0 rowlen = 15 # split into rows of this length... rowlen0 = rowlen rowlen1 = rowlen characters = dict() level = 'N' weight = 'k' print "char=", char if level_start == level_stop: level = level_start count_min = weight_start count_max = weight_stop if (weight_stop - weight_start + 1) < rowlen: rowlen0 = weight_stop - weight_start + 1 if weight_start == weight_stop: weight = weight_start count_min = level_start count_max = level_stop if (level_stop - level_start + 1) < rowlen: rowlen0 = level_stop - level_start + 1 # else: # return "" tbl = dict() if (char == 0): tbl['header'] = '' # Dimension of \( S_{'+str(weight)+'}('+str(level)+',\chi_{n})\)' charst = "" else: # s = 'Dimension of \( S_{'+str(weight)+'}('+str(level)+')\)' # s += ' (trivial character)' charst = ",\chi_{%s}" % char tbl['header'] = '' tbl['headersv'] = list() tbl['headersh'] = list() if weight == 'k': tbl['corner_label'] = "weight \(k\):" else: tbl['corner_label'] = "level \(N\):" tbl['data'] = list() tbl['data_format'] = 'html' tbl['class'] = "dimension_table" tbl['atts'] = "border=\"1\" class=\"nt_data\" padding=\"25\" width=\"100%\"" num_rows = ceil(QQ(count_max - count_min + 1) / QQ(rowlen0)) print "num_rows=", num_rows for i in range(1, rowlen0 + 1): tbl['headersh'].append(i + count_min - 1) if level_start == level_stop: st = "Dimension of \(S_{k}(%s%s) \):" % (level, charst) tbl['headersv'] = [st] else: st = "Dimension of \(S_{%s}(N%s) \):" % (weight, charst) tbl['headersv'] = [st] tbl['headersv'].append('Link to space:') # make a dummy table first # num_rows = (num_rows-1)*2 for r in range(num_rows * 2): row = [] for k in range(1, rowlen0 + 1): row.append("") tbl['data'].append(row) tbl['data_format'] = dict() for k in range(0, rowlen0): tbl['data_format'][k] = 'html' print "nu_rows=", len(tbl['data']) print "num_cols=", rowlen0 print "num_cols=", [len(r) for r in tbl['data']] for r in range(num_rows): for k in range(0, rowlen0): cnt = count_min + r * rowlen0 + k if level_start == level_stop: weight = cnt else: level = cnt url = url_for('emf.render_elliptic_modular_forms', level=level, weight=weight) if (cnt > count_max or cnt < count_min): tbl['data'][2 * r][k] = "" continue # s="<a name=\"#%s,%s\"></a>" % (level,weight) if (char == 0): d = dimension_cusp_forms(level, weight) else: x = DirichletGroup(level)[char] d = dimension_cusp_forms(x, weight) tbl['data'][2 * r][k] = str(d) if d > 0: s = "\(S_{%s}(%s)\)" % (weight, level) ss = "<a href=\"" + url + "\">" + s + "</a>" tbl['data'][2 * r + 1][k] = ss # else: # tbl['data'][2*r+1][k]="\(\emptyset\)" # ss = make_table_of_characters(level,weight) # tbl['data'][2*r+1][k]=ss # tbl['data'][r][k]=s # print "row=",row # tbl['data'][r]=row # print "tbl=",tbl s = html_table(tbl) # s=s+"\n <br> \(N="+str(rowlen0)+"\cdot row+col\)" # print "SS=",s return s
def product_space(chi, k, weights=False, base_ring=None, verbose=False): r""" Computes all eisenstein series, and products of pairs of eisenstein series of lower weight, lying in the space of modular forms of weight $k$ and nebentypus $\chi$. INPUT: - chi - Dirichlet character, the nebentypus of the target space - k - an integer, the weight of the target space OUTPUT: - a matrix of coefficients of q-expansions, which are the products of Eisenstein series in M_k(chi). WARNING: It is only for principal chi that we know that the resulting space is the whole space of modular forms. """ if weights == False: weights = srange(1, k / 2 + 1) weight_dict = {} weight_dict[-1] = [w for w in weights if w % 2] # Odd weights weight_dict[1] = [w for w in weights if not w % 2] # Even weights try: N = chi.modulus() except AttributeError: if chi.parent() == ZZ: N = chi chi = DirichletGroup(N)[0] Id = DirichletGroup(1)[0] if chi(-1) != (-1)**k: raise ValueError('chi(-1)!=(-1)^k') sturm = ModularForms(N, k).sturm_bound() + 1 if N > 1: target_dim = dimension_modular_forms(chi, k) else: target_dim = dimension_modular_forms(1, k) D = DirichletGroup(N) # product_space should ideally be called over number fields. Over complex # numbers the exact linear algebra solutions might not exist. if base_ring == None: base_ring = CyclotomicField(euler_phi(N)) Q = PowerSeriesRing(base_ring, 'q') q = Q.gen() d = len(D) prim_chars = [phi.primitive_character() for phi in D] divs = divisors(N) products = Matrix(base_ring, []) indexlist = [] rank = 0 if verbose: print(D) print('Sturm bound', sturm) #TODO: target_dim needs refinment in the case of weight 2. print('Target dimension', target_dim) for i in srange(0, d): # First character phi = prim_chars[i] M1 = phi.conductor() for j in srange(0, d): # Second character psi = prim_chars[j] M2 = psi.conductor() if not M1 * M2 in divs: continue parity = psi(-1) * phi(-1) for t1 in divs: if not M1 * M2 * t1 in divs: continue #TODO: THE NEXT CONDITION NEEDS TO BE CORRECTED. THIS IS JUST A TEST if phi.bar() == psi and not ( k == 2): #and i==0 and j==0 and t1==1): E = eisenstein_series_at_inf(phi, psi, k, sturm, t1, base_ring).padded_list() try: products.T.solve_right(vector(base_ring, E)) except ValueError: products = Matrix(products.rows() + [E]) indexlist.append([k, i, j, t1]) rank += 1 if verbose: print('Added ', [k, i, j, t1]) print('Rank is now', rank) if rank == target_dim: return products, indexlist for t in divs: if not M1 * M2 * t1 * t in divs: continue for t2 in divs: if not M1 * M2 * t1 * t2 * t in divs: continue for l in weight_dict[parity]: if l == 1 and phi.is_odd(): continue if i == 0 and j == 0 and (l == 2 or l == k - 2): continue #TODO: THE NEXT CONDITION NEEDS TO BE REMOVED. THIS IS JUST A TEST if l == 2 or l == k - 2: continue E1 = eisenstein_series_at_inf( phi, psi, l, sturm, t1 * t, base_ring) E2 = eisenstein_series_at_inf( phi**(-1), psi**(-1), k - l, sturm, t2 * t, base_ring) #If chi is non-principal this needs to be changed to be something like chi*phi^(-1) instead of phi^(-1) E = (E1 * E2 + O(q**sturm)).padded_list() try: products.T.solve_right(vector(base_ring, E)) except ValueError: products = Matrix(products.rows() + [E]) indexlist.append([l, k - l, i, j, t1, t2, t]) rank += 1 if verbose: print('Added ', [l, k - l, i, j, t1, t2, t]) print('Rank', rank) if rank == target_dim: return products, indexlist return products, indexlist