From K.M.Buzzard@dpmms.cam.ac.uk Wed Apr 8 05:22:40 1998 Return-Path: Received: from emu.dpmms.cam.ac.uk (emu.dpmms.cam.ac.uk [131.111.24.1]) by bmw.autobahn.org (8.8.7/8.8.7) with ESMTP id FAA13775 for ; Wed, 8 Apr 1998 05:14:52 -0700 Received: from buzzard (helo=localhost) by emu.dpmms.cam.ac.uk with local-smtp (Exim 1.90 #4) for was@bmw.autobahn.org id 0yMtie-0003cd-00; Wed, 8 Apr 1998 13:12:44 +0100 Date: Wed, 8 Apr 1998 13:12:43 +0100 (BST) From: Kevin Buzzard To: was@bmw.autobahn.org Subject: Re: back In-Reply-To: Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII Status: RO X-Status: > I read Cremona's stuff on the airplane home. I tell you as soon as I have > a good program for Gamma_1(N) with a character and Luiz's (or some other > library's) sparse matrix routines. I'm finding explicit computations on > modular forms to be fabulous. It's all making sense. Yeah, explicit calculations are the business. Actually, I should give you a small-but-growing program I have which calculates various q-expansions and which I use for testing random things. I'll insert it at the end with some comments. > Here's a vague question for you: > There are a whole bunch of spaces of modular forms > S_k(Gamma), M_k(Gamma), for various weights k and congruence > subgroups Gamma in SL(2,Z). How do they all fit together???? > I know about the maps from S_2(Gamma_0(N)) to S_2(Gamma_0(NM)). > But surely there are other maps connecting the various weights. You > mentioned something about this on the way to the restaurant last night. > What's a vague sort of intuition about how these things fit together? Here's a vague answer. If Gamma is contained in Delta is contained in SL_2(Z) are all congruence subgroups, then by definition, essentially, a modular form for Delta is a modular form for Gamma, because for a function to be a modular form for Gamma is only asking for it to transform well under Gamma, which is less than asking it to transform well under Delta. So S_k(Delta)\subseteq S_k(Gamma) for all k. More generally, if there is some g in GL_2(Q) such that g.Gamma.g^{-1} is a subset of Delta, and f is a form for Delta, then f|g is a form for Gamma, because (f|g)|gamma=f|(g.gamma)=f|(delta.g)=(f|delta)|g=f|g. Here by |g I mean "transform in the usual way", so that if f is a modular form then f|g=f for all g in the congruence subgroup. Using this trick one can get lots of relations. For example, if Mt divides N, with M,t,N all positive integers, then setting g=(t,0;0,1) one can check that if f(z) is in S_k(Gamma_0(M)) then the functions z|->f(tz) is in S_k(Gamma_0(N)). If you fix M and N, and vary t, then q-expansion calculations give you even more information. For example, the images of S_k(Gamma_0(N)) in S_k(Gamma_0(Np)) given by t=1 and t=p have trivial intersection if p is prime to N, although as I recall the proof of this is a little tricky if you don't know Atkin-Lehner theory. All the Gamma_0s can be replaced by Gamma_1s in this last paragraph. If f is on S_k(Gamma) and g is on S_l(Gamma) then fg is on S_{k+l}(Gamma). Caution: if f and g are eigenforms then fg might not be. For example, Delta^2 certainly isn't an eigenform in S_{24}(SL_2(Z)). Geometrically a modular form is a section of a sheaf on a modular curve. If you have a map between the modular curves then you can pull back the sheaf on the bottom curve, and if there is a map to the sheaf on the top curve then you can get a map between the spaces of modular forms. This is in fact what is going on with these g.Gamma.h^{-1} in Delta things, one can check that the map z|->gz on the upper half plane induces a map between certain modular curves and this is a "geometric" explanation of why what I was saying above works. The geometric explanation of multiplying things together is that modular forms of weight k are just sections of omega^{\otimes k} on the modular curve, where omega is a certain invertible sheaf, and there is a natural isomorphism omega^k tensor omega^l-->omega^{k+1} which induces a natural map (not nec an iso because tensor is one of those things that is only a presheaf) from global sections of omega^k tensor global sections of omega^l to global sections of omega^{k+1}. The elementary arguments are easier to understand though, and are what I learned first. Mod p there are some more subtle things. For example, I mentioned that S_{p+1}(SL_2(Z)) and S_2(Gamma_0(p)) have the same dimension, this is because they are "the same space mod p" in a very vague sense. But there is no map between them, as far as I know, in char 0. Modular forms, cusp forms, I've been switching between the two but it's all the same for both. I'll write you a crash course on Jacquet-Langlands theory one day and you can decide whether implementing it would be easier or harder than what Cremona does. My gut feeling is that calculating with definite quaternion algebras is always easier, but of course you get less modular forms here by JL. OK, finally, here's some gp routines which produce some modular forms. If you find anything wrong with them then do feel free to tell me! *** START HERE *** /* This is some useful things written for Pari-2 which do computations in spaces of modular forms for Gamma_1(N). Modular forms are stored in 2 ways: either as vectors [a_0,a_1,a_2,...,a_n] or as q-expansions a_0+a_1*q+a_2*q^2+...+a_n*q^n+(q^(n+1)). Conversion between the 2 formats is done by v=cov(f) and f=cof(v). Multiplication of vectors is much slower than multiplication of functions, but less likely to overflow the machine! Maybe I should be writing this whole thing in c++... WARNING: I just knocked this up one day, it kind of works, but I certainly cannot say that it definitely works all the time. I haven't checked that I implemented the functions right, but they don't seem to give me any contradictions so far. Use this program at your own peril :) Characters are stored in the rather tedious form eps=[eps(0),eps(1),...,eps(n-1)] and although they don't have to be primitive, it's a nice rule to try and keep them that way. Hecke operators need for the form to be a form of some character. One of the reasons I wrote this is that I'm sick of continually having to poke about trying to remember what the form of weight 1 and character chi and level p is, what the Eisenstein series of level 1 and weight k, what Shimura's trick is for producing cusp forms of small level/weight, what the general Eisenstein series is, what you can get from Theta series, and blah blah blah. Remark: f=sum(n=1,2000,random(20)*q^n)+O(q^2001) is quicker than f=sum(n=1,2000,random(20)*q^n,O(q^2001)), I guess for obvious reasons. */ \\ cov(f,d=0,g=0)=g=if(subst(truncate(f),q,0),Vec(f),d=Vec(f+1);d[1]=0;d) \\ NB COV DOES NOT WORK IF THE THING IS NOT +O(q^s) AT THE END! cof(v)=Ser(v,q) ch(eps,n)=eps[(n%length(eps))+1] tpf(f,p,k,eps,d=0)=if(length(eps)%p==0,print("p divides the level---use upf"),d=length(cov(f));sum(i=0,(d-1)/p,q^i*coeff(f,i*p))+p^(k-1)*ch(eps,p)*subst(f+O(q^ceil(d/(p^2))),q,q^p)+O(q^(floor((d-1)/p)+1))) upf(f,p)=sum(i=0,(length(cov(f))-1)/p,q^i*coeff(f,i*p))+O(q^(floor((length(cov(f))-1)/p)+1)) tpv(v,p,k,eps,d=0,e=0)=if(length(eps)%p==0,print("p divides the level---use upv"),d=length(v);e=p^(k-1)*ch(eps,p);vector(floor((d-1)/p)+1,n,v[(n-1)*p+1]+if((n-1)%p==0,e*v[(n-1)/p+1],0))) upv(v,p)=vector(floor((length(v)-1)/p)+1,n,v[(n-1)*p+1]) vadd(v,w)=cov(cof(v)+cof(w)) vmul(v,w)=vector(min(length(v),length(w)),n,sum(i=0,n-1,v[i+1]*w[n-i])) \\ This vmul is muuch sloower than cov(cof(v)*cof(w)) but much more \\ likely not to overflow the machine... \\ \\ That's it so far for the functions. \\ \\ Now for some pre-defined modular forms. ei(k,s)=if(k<4 || k%2, print("k must be an even integer, at least 4"),1-2*k/bernvec(k/2)[k/2+1]*sum(n=1,s,sigma(n,k-1)*q^n,0)+O(q^(s+1))) print("ei(k,s) is the level 1 Eisenstein series of weight k (up to O(q^{s+1}), as are all the others).") \\ That's the Eisenstein series of level 1 and weight k (k even, >=2) \\ \\ Here's Ken's weight 1 thing from his paper. eps(-1)=-1, and eps has \\ conductor and level equal to p \\ ei1(eps,s,p=0)=p=length(eps);if(isprime(p)==0 || p==2,print("eps has to be a character of odd prime level"),if(ch(eps,-1)!=-1,print("eps has to be an odd character"),l0(eps)/2+sum(n=1,s,sigeps(eps,n,1)*q^n)+O(q^(s+1)))) print("ei1(eps,s) is the Eisenstein series of weight 1 and level p (prime) associated to the odd character eps.") ei2(eps,s,l=0)=l=length(eps);if(ch(eps,-1)!=1 || sum(i=1,l,eps[i])==eulerphi(l),print("eps has to be a nontrivial even character for weight 2"),sum(n=1,s,sigeps(eps,n,0)*n*q^n)+O(q^(s+1))) print("ei2(eps,s) is the Eisenstein series E_2(z;eps,1) in Miyake.") ei3(eps,s,l=0)=l=length(eps);if(ch(eps,-1)!=-1,print("eps has to be an odd character for weight 3"),sum(n=1,s,sigeps(eps,n,-1)*n^2*q^n)+O(q^(s+1))) print("ei3(eps,s) is the Eisenstein series E_3(z;eps,1) in Miyake.") sigeps(eps,n,k,t=0)=t=divisors(n);sum(i=1,length(t),t[i]^(k-1)*ch(eps,t[i])) l0(eps,p=0)=p=length(eps);if(isprime(p)==0 || p==2,print("eps must be a char of odd prime level"),-1/p*sum(n=1,p-1,eps[n+1]*(n-p/2))) g2(eps,s)=p=length(eps);if(isprime(p)==0 || p==2,print("eps has to be a non-trivial character of odd prime level"),if(ch(eps,p-1)!=1 || sum(i=1,p,eps[i])==p,print("eps has to be even and non-trivial"),lm1(eps)/2+sum(n=1,s,sigeps(eps,n,2)*q^n)+O(q^(s+1)))) print("g2(eps,s) is the Eisenstein series of weight 2 of level p prime and character epsilon non-trivial mod p.") lm1(eps,p=0)=p=length(eps);if(isprime(p)==0 || p==2,print("eps must be a char of odd prime level"),-1/2/p*sum(n=1,p-1,eps[n+1]*(n^2-p*n+p^2/6))) s2(eps,s)=p=length(eps);if(isprime(p)==0 || p==2,print("eps has to be a non-trivial character of odd prime level"),if(ch(eps,-1)!=1 || sum(i=1,p,eps[i])==p-1,print("eps has to be a non-trivial even character"),sum(n=1,s,sigeps(eps,n,0)*n*q^n)+O(q^(s+1)))) print("s2(eps,s) is the Eisenstein series of weight 2 that vanishes at infinity, and is associated to a non-trivial character of conductor p prime.") g2triv(p,s)=if(!isprime(p),print("p has to be an odd prime"),(p-1)/24+sum(n=1,s,(sigma(n)-if(n%p,0,p*sigma(n/p)))*q^n)+O(q^(s+1))) print("g2triv(p,s) is the Eisenstein series for Gamma_0(p).") \\ \\ Now here's some modular forms from Shimura's book. \\ ex228(k) has weight k and is on Gamma_0(N) where N=24/k-1. For k=2,4,6,8. ex228(k)=if(k==8 || k==6 || k==4 || k==2,q*(eta(q)*eta(q^(24/k-1)))^k,print("k has to be one of 2,4,6,8. Check p49 of Shimura's book for more details.")) print("ex228(k) for k=2,4,6,8 is a cusp form of weight k on Gamma_0(24/k-1) defined in Shimura's book on p49. To set precision to q^n use \ps n") \\ \\ \\ Here's some programs to check to see if what you have really is a modular \\ form lm(s,pr)=1/2/Pi*pr*log(10)/s vl(f,t,pr)=if(imag(t) Received: from emu.dpmms.cam.ac.uk (exim@emu.dpmms.cam.ac.uk [131.111.24.1]) by ferrari.autobahn.org (8.8.7/8.8.7) with ESMTP id FAA07655 for ; Wed, 8 Apr 1998 05:54:27 -0700 Received: from buzzard (helo=localhost) by emu.dpmms.cam.ac.uk with local-smtp (Exim 1.90 #4) for was@bmw.autobahn.org id 0yMuMp-0003gu-00; Wed, 8 Apr 1998 13:54:15 +0100 Date: Wed, 8 Apr 1998 13:54:14 +0100 (BST) From: Kevin Buzzard To: was@bmw.autobahn.org Subject: Re: back In-Reply-To: Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII Status: RO X-Status: Oh yeah, it does Hecke operators too. I forgot about that. I think that was why I put in the vector thingies. ? \ps 100 seriesprecision = 100 significant terms ? f=eta(q)^24*q; ? \\ this is delta ? ?tpf tpf(f=0, p=0, k=0, eps=0, d=0) = if(length(eps)%p==0,print("p divides the level---use upf"),d=length(cov(f));sum(i=0,(d-1)/p,q^i*coeff(f,i*p))+p^(k-1)*ch(eps,p)*subst(f+O(q^ceil(d/(p^2))),q,q^p)+O(q^(floor((d-1)/p)+1))) ? tpf(f,2,12) p divides the level---use upf ? tpf(f,2,12,[1]) *** obsolete function: q^i*coeff(f,i*p))+p^(k-1 ^------------------- For full compatibility with GP 1.39.15, type "default(compatible,3)" (you can also set "compatible = 3" in your .gprc) New syntax: coeff(x,s) ===> polcoeff(x,s) polcoeff(x,s): coefficient of degree s of x, or the s-th component for vectors or matrices (for which it is simpler to use x[]). *** Well, it sometimes does them :) I think I might have had coeff aliased to polcoeff when I wrote it :) ? tpf(f,2,12,[1]) %19 = -24*q + 576*q^2 - 6048*q^3 + 35328*q^4 - 115920*q^5 + 145152*q^6 + 401856*q^7 - 2027520*q^8 + 2727432*q^9 + 2782080*q^10 - 12830688*q^11 + 8902656*q^12 + 13865712*q^13 - 9644544*q^14 - 29211840*q^15 - 23691264*q^16 + 165742416*q^17 - 65458368*q^18 - 255874080*q^19 + 170634240*q^20 + 101267712*q^21 + 307936512*q^22 - 447438528*q^23 - 510935040*q^24 + 611981400*q^25 - 332777088*q^26 + 1758697920*q^27 - 591532032*q^28 - 3081759120*q^29 + 701084160*q^30 + 1268236032*q^31 + 4720951296*q^32 - 3233333376*q ^33 - 3977817984*q^34 + 1940964480*q^35 - 4014779904*q^36 + 4373119536*q^37 + 6140977920*q^38 + 3494159424*q^39 - 9792921600*q^40 - 7394890608*q^41 - 2430425088*q^42 + 411016992*q^43 + 18886772736*q^44 + 13173496560*q^45 + 10738524672*q^46 - 64496363904*q ^47 - 5970198528*q^48 + 40727164968*q^49 - 14687553600*q^50 + O(q^51) ? %19+24*f %22 = O(q^51) ? *** That's more like it :) K