Karim Belabas on Thu, 30 Aug 2018 13:04:34 +0200 |
[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]
Re: inconsistency in mfcoefs |
* John Cremona [2018-08-29 03:48]: > I am computing some modular forms, and their q-expansion coefficients a_n. > In cases where these are not rational, most of them are returned as > t_POLMOD e.g. Mod(t^3 - t^2 + t - 1, t^4 + t^3 + t^2 + t + 1), with the > same modulus throughout, and that is fine. However, sometimes *but not > always* a_0=0 ad a_1=1 with type t_INT, sometimes they look the same but > have type t_POLMOD, and sometimes they look like genuine polmods, e.g. > a_1=Mod(1, t^4 + t^3 + t^2 + t + 1). This inconsistency is causing bugs in > my programs. (example below). > > Right now I am just computing traces of these a_n, which is much harder > than it should be. For a genuine t_POLMOD value a_n, trace(a_n) gives the > right answer. If they happen to be t_INT then one hits the incredible > pari/gp convention that trace(1)=2, and in general the trace of any t_INT > doubles it. I for one think that is mad (if I want my 1 to be a complex > number I will tell you!). trace() is inconsistent (our convention to try and handle "every input", without context, or rather with an arbitrarily defined context), but not mad. We just define trace(x) := x + conj(x) and the inconsistency comes from our handling of polmods and matrices in a special, but "expected", way. But I agree that relying on "PARI's philosophy" on 'uncontrolled' objects is madness as soon as the situation becomes a little complicated. The right way to tackle this is to be explicit about the context and let your functions decide depending on it, not to rely on PARI types to convey some natural meaning and expect generic function to do the right thing. Here we have three number fields, Q \subset Q(chi) \subset Q(f) and 2 "natural" traces, the desired operation must be specified explicitly (see P.S. for how to implement relative traces) > \\ now ans 1 is: [[0, 1, Mod(t^3 - t^2 + t - 1, t^4 + t^3 + t^2 + t + 1), [...] > \\ now ans2 is: [[Mod(0, y^4 + 12*y^3 + 64*y^2 + 12288*y + 1048576), Mod(1, > y^4 + 12*y^3 + 64*y^2 + 12288*y + 1048576), Mod(y, y^4 + 12*y^3 + 64*y^2 + > 12288*y + 1048576),... That looks inconsistent but both are correct: 0 and 1 can be coerced to every field. You'll always get the first shape when the eigenform is defined over Q(chi) [ variable(f.mod) == 't ] and the second when it's not [ variable(f.mod) != 't, and in fact the variable is 'y ]. Note that the first case subdivides: when Q(f) = Q(chi) = Q, all coefficients are integers (t_INT), but f.mod = t - 1 = polcyclo(1,'t). If you're actually tracing from Q(f) to Q(chi), there's no need to do anything in the first case of course. And trace() will always work when Q(f) != Q(chi), in fact you only need to apply it to mftobasis(Snew, newforms[i]) then multiply by 'coeffs' as before. You can then trace from Q(chi) to Q if needed. As for the implied suggestion of changing mfcoefs to use Mod(0, t^4 + t^3 + t^2 + t + 1) and Mod(1, t^4 + t^3 + t^2 + t + 1) instead of 0 and 1, it would waste a few hours of work in order to complicate further the code base and achieve less efficient internal computation... [ the first thing we do internally is to get rid of all t_POLMODs and rely on chi / mffields instead ] Cheers, K.B. P.S. The simplest way to handle traces over a fixed simple number field is nf = nfinit(...) \\ done once of course nfelttrace(nf, a) Then, whatever the type of 'a' (t_INT, t_FRAC, t_POLMOD, t_POL, t_COL on nf.zk...), we get the right answer. And, once the precomputation dealt with, this is faster than trace() since Netwon sums for nf.pol are computed only once. For basic modular forms it would work, but it doesn't for eigenforms because their coefficients need not live in Q(\chi) but in a relative extension and we would have to use rnfinit + rnfelttrace. This also has the serious drawback of computing 'nf' or 'rnf' structures (in particular maximal orders) when we actually need much less information to compute the trace of an algebraic number. But there is no ready-made function for this, it's easy to write but not easy to interface within existing GP paradigms: \\ to trace from K[v] / (P(v)) to K mytraceinit(P) = [P, polsym(P, poldegree(P)-1)]; mytrace(TR, a) = { my ([P, v] = TR, d = poldegree(P)); a = liftpol(a); if (type(a) != "t_POL" || variable(a) != variable(P), return (d * a)); sum(i = 0, d-1, polcoeff(a,i) * v[i+1]); } TR = mytraceinit(P); mytrace(TR, a) \\ Tr_{L/K}(a), L = K[v]/P This is certainly going to be 10 times slower than trace() in your two simple examples, but it should be bulletproof. And probably faster in more complicated examples, e.g. in the generic case where eigenforms are not defined over Q(chi). Written in C, it would become faster than trace() because it moves part of trace() to precomputations at the slight cost of adding some sanity checks which unfortunately can't be implemented efficiently in GP (which further adds unnecessary copying in this case). -- Karim Belabas, IMB (UMR 5251) Tel: (+33) (0)5 40 00 26 17 Universite de Bordeaux Fax: (+33) (0)5 40 00 21 23 351, cours de la Liberation http://www.math.u-bordeaux.fr/~kbelabas/ F-33405 Talence (France) http://pari.math.u-bordeaux.fr/ [PARI/GP] `