John Cremona on Thu, 30 Aug 2018 14:43:39 +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]
`