forked from JohnCremona/CMFs
-
Notifications
You must be signed in to change notification settings - Fork 1
/
mf_pari.py
1257 lines (1119 loc) · 46.6 KB
/
mf_pari.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from char import NChars, char_orbit_index_to_DC_number
from mf_compare import polredbest_stable#, polredbest, polredabs
from sage.all import pari,ZZ,QQ, Rational,RR, GF, PolynomialRing, cyclotomic_polynomial, euler_phi, NumberField, Matrix, prime_range
from sage.rings.finite_rings.integer_mod import mod
from sage.libs.pari.convert_sage import gen_to_sage
from sage.libs.pari.all import PariError
import sys
import time
Qt = PolynomialRing(QQ,'t')
# some pari utilities
pari_x = pari("x")
pari_col1 = pari("M -> M[,1]")
pari_trace_product = pari("(M1,M2) -> [m,n]=matsize(M1); sum(i=1,m,sum(j=1,n,M1[i,j]*M2[j,i]))")
def pari_row_slice(r1,r2):
return pari("M -> M[{}..{},]".format(r1,r2))
def pari_trace(d):
return pari('c->if(type(c)=="t_POLMOD",trace(c),c*{})'.format(d))
def pari_e1(d):
return pari("{}~".format([int(j==0) for j in range(d)]))
def abstrace(x,deg):
# absolute trace of a gp int / polmod / polmod pver polmod
# trace of a t_POLMOD does what is expected but trace of an int
# doubles it. Also we might see an int as one coefficient of a newform
# most of whose coefficients are t_POLMODs. In this case we need to
# multiply by the appropriate degree, so have to pass the degree as a
# parameter.
#print("abstrace({}) in degree {}".format(x,deg))
if deg==1:
return x.sage()
if x in QQ: # miraculously this works for a GpElement
#print("---returns(2) {}".format(deg*QQ(x)))
return deg*Rational(x)
try:
#print("---returns(3) {}".format(x.trace().sage()))
return x.trace().sage()
except NameError:
return x.trace().trace().sage()
def NewSpace(N, k, chi_number,Detail=0):
G = pari(N).znstar(1)
chi_dc = char_orbit_index_to_DC_number(N,chi_number)
chi_gp = G.znconreylog(chi_dc)
chi_order = ZZ(G.charorder(chi_gp))
chi_degree = euler_phi(chi_order)
if Detail:
print("order(chi) = {}, [Q(chi):Q] = {}".format(chi_order, chi_degree))
print("pari character = {}".format(chi_gp))
NK = [N,k,[G,chi_gp]]
Snew = pari(NK).mfinit(0)
rel_degree = Snew.mfdim()
dim = chi_degree*rel_degree
if Detail:
print("Relative dimension = {}".format(rel_degree))
print("dim({}:{}:{}) = {}*{} = {}".format(N,k,chi_number,chi_degree,rel_degree,dim))
return NK, Snew
def is_semisimple_modular(M, m, nprimes=5):
"""M is a pari matrix over Q(zeta_m). Check if M mod p has squarefree
char poly for nprimes primes p=1 (mod m). If True for any p,
return True since then the char poly of M itself must be
square-free. If False for all p, return False since the M
probably has non-squarefree char poly. There may therefore be
false negatives.
"""
pol = cyclotomic_polynomial(m)
pt = pari("t")
np=0
for p in prime_range(1000000):
if m>1 and not p%m==1:
continue
np+=1
#print("testing modulo p={}".format(p))
if np>nprimes:
#print("op not semisimple modulo {} primes, so returning False".format(nprimes))
return False
zmodp = pari(pol.roots(GF(p))[0][0])
#print("zmodp = {}".format(zmodp))
try:
Mmodp = M.lift()*pari(mod(1,p))
#print("Lifted matrix = {}".format(Mmodp))
Mmodp = Mmodp.subst(pt,zmodp)
#print("matrix (mod {}) = {}".format(p,Mmodp))
modpcharpoly = Mmodp.charpoly()
#print("char poly (mod {}) = {}".format(p,modpcharpoly))
if modpcharpoly.issquarefree():
#print("op is semisimple mod {}".format(p))
return True
else:
#print("op is not semisimple mod {}".format(p))
pass
except PariError: ## happens if M not integral mod p
np-=1
def Newforms_v1(N, k, chi_number, dmax=20, nan=100, Detail=0):
t0=time.time()
G = pari(N).znstar(1)
chi_dc = char_orbit_index_to_DC_number(N,chi_number)
chi_gp = G.znconreylog(chi_dc)
chi_order = ZZ(G.charorder(chi_gp))
if Detail:
print("Decomposing space {}:{}:{}".format(N,k,chi_number))
NK = [N,k,[G,chi_gp]]
pNK = pari(NK)
if Detail>1:
print("NK = {} (gp character = {})".format(NK,chi_gp))
SturmBound = pNK.mfsturm()
Snew = pNK.mfinit(0)
total_dim = Snew.mfdim()
# Get the character polynomial
# Note that if the character order is 2*m with m odd then Pari uses the
# m'th cyclotomic polynomial and not the 2m'th (e.g. for a
# character of order 6 it uses t^2+t+1 and not t^2-t+1).
chi_order_2 = chi_order//2 if chi_order%4==2 else chi_order
chipoly = cyclotomic_polynomial(chi_order_2,'t')
chi_degree = chipoly.degree()
assert chi_degree==euler_phi(chi_order)==euler_phi(chi_order_2)
if Detail:
print("Computed newspace {}:{}:{}, dimension={}*{}={}, now splitting into irreducible subspaces".format(N,k,chi_number, chi_degree,total_dim,chi_degree*total_dim))
if Detail>1:
print("Sturm bound = {}".format(SturmBound))
print("character order = {}".format(chi_order))
# Get the relative polynomials: these are polynomials in y with coefficients either integers or polmods with modulus chipoly
pols = Snew.mfsplit(0,1)[1]
if Detail>2: print("pols[GP] = {}".format(pols))
nnf = len(pols)
dims = [chi_degree*f.poldegree() for f in pols]
if nnf==0:
if Detail:
print("The space {}:{}:{} is empty".format(N,k,chi_number))
return []
if Detail:
print("The space {}:{}:{} has {} newforms, dimensions {}".format(N,k,chi_number,nnf,dims))
# Get the traces. NB (1) mftraceform will only give the trace
# form on the whole space so we only use this when nnf==1,
# i.e. the space is irreducible. Otherwise we'll need to compute
# traces from the ans. (2) these are only traces down to Q(chi)
# so when that has degree>1 (and only then) we need to take an
# additional trace.
traces = [None for _ in range(nnf)]
if nnf==1:
d = ZZ(chi_degree * (pols[0]).poldegree())
if Detail:
print("Only one newform so use traceform to get traces")
traces = pNK.mftraceform().mfcoefs(nan)
if Detail>1:
print("raw traces: {}".format(traces))
if chi_degree>1:
# This is faster than the more simple
# traces = [c.trace() for c in traces]
gptrace = pari_trace(chi_degree)
traces = pari.apply(gptrace,traces)
if Detail>1:
print("traces to QQ: {}".format(traces))
traces = gen_to_sage(traces)[1:]
traces[0] = d
if Detail>1:
print("final traces: {}".format(traces))
traces = [traces]
# Get the coefficients an. We'll need these for a newform f if
# either (1) its dimension is >1 and <= dmax, when we want to
# store them, or (2) there is more than one newform, so we can
# later compute the traces from them. So we don't need them if
# nnf==1 and the dimension>dmax.
if dmax==0 or nnf>1 or ((chi_degree*(pols[0]).poldegree())<=dmax):
if Detail>1:
print("...computing mfeigenbasis...")
newforms = Snew.mfeigenbasis()
if Detail>1:
print("...computing {} mfcoefs...".format(nan))
coeffs = Snew.mfcoefs(nan)
ans = [coeffs*Snew.mftobasis(nf) for nf in newforms]
if Detail>2:
print("ans[GP] = {}".format(ans))
else:
# there is only one newform (so we have the traces) and its
# dimension is >dmax (so we will not need the a_n):
ans = [None for _ in range(nnf)]
newforms = [None for _ in range(nnf)]
# Compute AL-eigenvalues if character is trivial:
if chi_order==1:
Qlist = [(p,p**e) for p,e in ZZ(N).factor()]
ALs = [gen_to_sage(Snew.mfatkineigenvalues(Q[1])) for Q in Qlist]
if Detail: print("ALs: {}".format(ALs))
# "transpose" this list of lists:
ALeigs = [[[Q[0],ALs[i][j][0]] for i,Q in enumerate(Qlist)] for j in range(nnf)]
if Detail: print("ALeigs: {}".format(ALeigs))
else:
ALeigs = [[] for _ in range(nnf)]
Nko = (N,k,chi_number)
# print("len(traces) = {}".format(len(traces)))
# print("len(newforms) = {}".format(len(newforms)))
# print("len(pols) = {}".format(len(pols)))
# print("len(ans) = {}".format(len(ans)))
# print("len(ALeigs) = {}".format(len(ALeigs)))
pari_nfs = [
{ 'Nko': Nko,
'SB': SturmBound,
'chipoly': chipoly,
'pari_newform': newforms[i],
'poly': pols[i],
'ans': ans[i],
'ALeigs': ALeigs[i],
'traces': traces[i],
} for i in range(nnf)]
# This processing returns full data but the polynomials have not
# yet been polredbested and the an coefficients have not been
# optimized (or even made integral):
#return pari_nfs
t1=time.time()
if Detail:
print("{}: finished constructing GP newforms (time {:0.3f})".format(Nko,t1-t0))
nfs = [process_pari_nf_v1(nf, dmax, Detail) for nf in pari_nfs]
if len(nfs)>1:
nfs.sort(key=lambda f: f['traces'])
t2=time.time()
if Detail:
print("{}: finished first processing of newforms (time {:0.3f})".format(Nko,t2-t1))
if Detail>2:
for nf in nfs:
if 'eigdata' in nf:
print(nf['eigdata']['ancs'])
nfs = [bestify_newform(nf,dmax,Detail) for nf in nfs]
t3=time.time()
if Detail:
print("{}: finished bestifying newforms (time {:0.3f})".format(Nko,t3-t2))
if Detail>2:
for nf in nfs:
if 'eigdata' in nf:
print(nf['eigdata']['ancs'])
nfs = [integralify_newform(nf,dmax,Detail) for nf in nfs]
t4=time.time()
if Detail:
print("{}: finished integralifying newforms (time {:0.3f})".format(Nko,t4-t3))
if Detail>2:
for nf in nfs:
if 'eigdata' in nf:
print(nf['eigdata']['ancs'])
print("Total time for space {}: {:0.3f}".format(Nko,t4-t0))
return nfs
def Newforms_v2(N, k, chi_number, dmax=20, nan=100, Detail=0):
t0=time.time()
G = pari(N).znstar(1)
chi_dc = char_orbit_index_to_DC_number(N,chi_number)
chi_gp = G.znconreylog(chi_dc)
chi_order = ZZ(G.charorder(chi_gp))
if Detail:
print("Decomposing space {}:{}:{}".format(N,k,chi_number))
NK = [N,k,[G,chi_gp]]
pNK = pari(NK)
if Detail>1:
print("NK = {} (gp character = {})".format(NK,chi_gp))
SturmBound = pNK.mfsturm()
Snew = pNK.mfinit(0)
total_dim = Snew.mfdim() # this is the relative dimension i.e. degree over Q(chi)
# Get the character polynomial
# Note that if the character order is 2*m with m odd then Pari uses the
# m'th cyclotomic polynomial and not the 2m'th (e.g. for a
# character of order 6 it uses t^2+t+1 and not t^2-t+1).
chi_order_2 = chi_order//2 if chi_order%4==2 else chi_order
chipoly = cyclotomic_polynomial(chi_order_2,'t')
chi_degree = chipoly.degree()
assert chi_degree==euler_phi(chi_order)==euler_phi(chi_order_2)
t05=time.time()
if Detail:
print("Computed newspace {}:{}:{} in {:0.3f}, dimension={}*{}={}, now splitting into irreducible subspaces".format(N,k,chi_number, t05-t0, chi_degree,total_dim,chi_degree*total_dim))
if Detail>1:
print("Sturm bound = {}".format(SturmBound))
print("character order = {}".format(chi_order))
if total_dim==0:
if Detail:
print("The space {}:{}:{} is empty".format(N,k,chi_number))
return []
# First just compute Hecke matrices one at a time, to find a splitting operator
def Hecke_op_iter():
p=ZZ(1)
while True:
p = p.next_prime()
# while p.divides(N):
# p=p.next_prime()
#print("Computing T_{}".format(p))
yield p, Snew.mfheckemat(p)
Tp_iter = Hecke_op_iter()
p, op = Tp_iter.next()
s1=time.time()
if Detail:
print("testing T_{}".format(p))
ok = is_semisimple_modular(op,chi_order_2)
# f = op.charpoly()
# ok = f.issquarefree()
if ok:
if Detail:
print("Lucky first time: semisimple. Finding char poly")
f = op.charpoly()
ops=[(p,op)]
while not ok:
pi, opi = Tp_iter.next()
if Detail:
print("testing T_{}".format(pi))
ok = is_semisimple_modular(op,chi_order_2)
# f = opi.charpoly()
# ok = f.issquarefree()
if ok:
if Detail:
print("success using T_{}. Finding char poly".format(pi))
op = opi
f = op.charpoly()
break
else:
#ops.append((pi,opi))
ops += [(pi,opi)]
if Detail:
print("T_{} not semisimple".format(pi))
print("testing combinations...")
for j in range(5):
co = [ZZ.random_element(-5,5) for _ in ops]
while not co:
co = [ZZ.random_element(-5,5) for _ in ops]
if Detail:
print("Testing lin comb of {} ops with coeffs {}".format(len(co),co))
op = sum([ci*opj[1] for ci,opj in zip(co,ops)])
ok = is_semisimple_modular(op,chi_order_2)
# f=op.charpoly()
# ok = f.issquarefree()
if ok:
if Detail:
print("success using {}-combo of T_p for p in {}. Finding char poly".format(co,[opj[0] for opj in ops]))
f = op.charpoly()
break
if not ok:
raise RuntimeError("failed to find a 0,1-combination of Tp which is semisimple")
ffac = f.factor()
nnf = ffac.matsize()[0]
gp_pols = pari_col1(ffac)
pols = [pol for pol in gp_pols]
reldims = [pol.poldegree() for pol in pols]
dims = [d*chi_degree for d in reldims]
# We'll need the coefficients an, if any newforms have dimension >1 and <=dmax.
an_needed = [i for i,d in enumerate(dims) if d>1 and (dmax==0 or d<=dmax)]
if Detail:
print("Need to compute a_n for {} newforms: {}".format(len(an_needed), an_needed))
s2=time.time()
if Detail:
print("Computed splitting in {:0.3f}, # newforms = {}".format(s2-s1,nnf))
print("relative dims = {}, absolute dims = {}".format(reldims,dims))
# Compute AL-matrices if character is trivial:
if chi_order==1:
Qlist = [(pr,pr**e) for pr,e in ZZ(N).factor()]
ALs = [Snew.mfatkininit(Q[1])[1] for Q in Qlist]
if Detail:
print("AL-matrices:")
for Q,AL in zip(Qlist,ALs):
print("W_{}={}".format(Q[1],AL) )
if nnf==1 and dims[0]>dmax and dmax>0:
if Detail:
print("Only one newform and dim={}, so use traceform to get traces".format(dims[0]))
traces = pNK.mftraceform().mfcoefs(nan)
if Detail>1:
print("raw traces: {}".format(traces))
if chi_degree>1:
# This is faster than the more simple
# traces = [c.trace() for c in traces]
gptrace = pari_trace(chi_degree)
traces = pari.apply(gptrace,traces)
if Detail>1:
print("traces to QQ: {}".format(traces))
traces = gen_to_sage(traces)[1:]
traces[0] = dims[0]
if Detail>1:
print("final traces: {}".format(traces))
traces = [traces]
else: # >1 newform, or just one but its absolute dim is <=dmax
hs = [f/fi for fi in pols]
if Detail>1:
print("fs: {}".format(pols))
print("hs: {}".format(hs))
print(" with degrees {}".format([h.poldegree() for h in hs]))
if Detail>1:
print("Starting to compute gcds")
As = [(hi*(fi.gcdext(hi)[2])).subst(pari_x,op) for fi,hi in zip(pols,hs)]
if Detail:
print("Computed idempotent matrix decomposition")
ims = [A.matimage() for A in As]
U = pari.matconcat(ims)
Uinv = U**(-1)
if Detail:
print("Computed U and U^-1")
starts = [1+sum(d for d in reldims[:i]) for i in range(len(reldims))]
stops = [sum(d for d in reldims[:i+1]) for i in range(len(reldims))]
slicers = [pari_row_slice(r1,r2) for r1,r2 in zip(starts,stops)]
ums = [slice(Uinv) for slice in slicers]
imums = [imA*umA for imA,umA in zip(ims,ums)]
s3=time.time()
if Detail:
print("Computed projectors in {:0.3f}".format(s3-s2))
print("Starting to compute {} Hecke matrices T_n".format(nan))
heckemats = Snew.mfheckemat(pari(range(1,nan+1)))
s4=time.time()
if Detail:
print("Computed {} Hecke matrices in {:0.3f}s".format(nan,s4-s3))
# If we are going to compute any a_n then we now compute
# umA*T*imA for all Hecke matrices T, whose traces give the
# traces and whose first columns (or any row or column) give
# the coefficients of the a_n with respect to some
# Q(chi)-basis for the Hecke field.
# But if we only need the traces then it is faster to
# precompute imA*umA=imAumA and then the traces are
# trace(imAumA*T). NB trace(UMV)=trace(VUM)!
if Detail:
print("Computing traces")
# Note that computing the trace of a matrix product is faster
# than first computing the product and then the trace:
gptrace = pari('c->if(type(c)=="t_POLMOD",trace(c),c*{})'.format(chi_degree))
traces = [[gen_to_sage(gptrace(pari_trace_product(T,imum))) for T in heckemats] for imum in imums]
s4=time.time()
if Detail:
print("Computed traces to Z in {:0.3f}".format(s4-s3))
for tr in traces:
print(tr[:20])
ans = [None for _ in range(nnf)]
bases = [None for _ in range(nnf)]
if an_needed:
if Detail:
print("...computing a_n...")
for i in an_needed:
dr = reldims[i]
if Detail:
print("newform #{}/{}, relative dimension {}".format(i,nnf,dr))
# method: for each irreducible component we have matrices
# um and im (sizes dr x n and n x dr where n is the
# relative dimension of the whole space) such that for
# each Hecke matrix T, its restriction to the component is
# um*T*im. To get the eigenvalue in a suitable basis all
# we need do is take any one column (or row): we choose
# the first column. So the computation can be done as
# um*(T*im[,1]) (NB the placing of parentheses).
imsicol1 = pari_col1(ims[i])
umsi = ums[i]
ans[i] = [(umsi*(T*imsicol1)).Vec() for T in heckemats]
if Detail:
print("ans done")
if Detail>1:
print("an: {}...".format(ans[i]))
# Now compute the bases (of the relative Hecke field over
# Q(chi) w.r.t which these coefficients are given. Here
# we use e1 because we picked the first column just now.
B = ums[i]*op*ims[i]
e1 = pari_e1(dr)
cols = [e1]
while len(cols)<dr:
cols.append(B*cols[-1])
W = pari.matconcat(cols)
bases[i] = W.mattranspose()**(-1)
if Detail>1:
print("basis = {}".format(bases[i].lift()))
# Compute AL-eigenvalues if character is trivial:
if chi_order==1:
ALeigs = [[
[Q[0], gen_to_sage((umA*(AL*(pari_col1(imA))))[0])]
for Q,AL in zip(Qlist,ALs)]
for umA,imA in zip(ums,ims)]
if Detail>1: print("ALeigs: {}".format(ALeigs))
else:
ALeigs = [[] for _ in range(nnf)]
Nko = (N,k,chi_number)
#print("len(traces) = {}".format(len(traces)))
#print("len(newforms) = {}".format(len(newforms)))
#print("len(pols) = {}".format(len(pols)))
#print("len(ans) = {}".format(len(ans)))
#print("len(ALeigs) = {}".format(len(ALeigs)))
pari_nfs = [
{ 'Nko': Nko,
'SB': SturmBound,
'chipoly': chipoly,
'poly': pols[i],
'ans': ans[i],
'basis': bases[i],
'ALeigs': ALeigs[i],
'traces': traces[i],
} for i in range(nnf)]
# We could return these as they are but the next processing step
# will fail if the underlying gp process has quit, so we do the
# processing here.
# This processing returns full data but the polynomials have not
# yet been polredbested and the an coefficients have not been
# optimized (or even made integral):
#return pari_nfs
t1=time.time()
if Detail:
print("{}: finished constructing pari newforms (time {:0.3f})".format(Nko,t1-t0))
nfs = [process_pari_nf(nf, dmax, Detail) for nf in pari_nfs]
if len(nfs)>1:
nfs.sort(key=lambda f: f['traces'])
t2=time.time()
if Detail:
print("{}: finished first processing of newforms (time {:0.3f})".format(Nko,t2-t1))
if Detail>2:
for nf in nfs:
if 'eigdata' in nf:
print(nf['eigdata']['ancs'])
nfs = [bestify_newform(nf,dmax,Detail) for nf in nfs]
t3=time.time()
if Detail:
print("{}: finished bestifying newforms (time {:0.3f})".format(Nko,t3-t2))
if Detail>2:
for nf in nfs:
if 'eigdata' in nf:
print(nf['eigdata']['ancs'])
nfs = [integralify_newform(nf,dmax,Detail) for nf in nfs]
t4=time.time()
if Detail:
print("{}: finished integralifying newforms (time {:0.3f})".format(Nko,t4-t3))
if Detail>2:
for nf in nfs:
if 'eigdata' in nf:
print(nf['eigdata']['ancs'])
print("Total time for space {}: {:0.3f}".format(Nko,t4-t0))
return nfs
Newforms = Newforms_v2
def process_pari_nf(pari_nf, dmax=20, Detail=0):
r"""
Input is a dict with keys 'Nko' (N,k,chi_number), 'chipoly', 'SB' (Sturm bound), 'pari_newform', 'poly', 'ans', 'ALeigs', 'traces'
Output adds 'traces' (unless already computed), and also 'eigdata' if 1<dimension<=dmax
We do not yet use polredbest or optimize an coeffients
NB This must be called on each newform *before* the GP process associated with the data has been terminated.
"""
Nko = pari_nf['Nko']
chipoly = pari_nf['chipoly']
poly = pari_nf['poly']
SB = pari_nf['SB']
ALeigs = pari_nf['ALeigs']
traces = pari_nf['traces']
assert traces # not None
ans = pari_nf['ans']
basis = pari_nf['basis']
# initialize with data needing no more processing:
newform = {'Nko': Nko, 'chipoly': chipoly, 'SB': SB, 'ALeigs':ALeigs, 'traces':traces}
# Set the polynomial. poly is the relative polynomial, in x (so
# just x if the top degree is 1) with coefficients either integers
# (if the bottom degree is 1) or polmods with modulus chipoly.
# rel_poly is in Qchi[x].
Qx = PolynomialRing(QQ,'x')
#pari_Qx_poly_to_sage = lambda f: Qx(gen_to_sage(f.Vecrev()))
Qchi = NumberField(chipoly,'t')
chi_degree = chipoly.degree()
# NB the only reason for negating the chi_degree parameter here is to get around a bug in the Sage/pari interface
pari_Qchi_to_sage = lambda elt: Qchi(gen_to_sage(elt.lift().Vecrev(-chi_degree)))
Qchi_x = PolynomialRing(Qchi,'x')
pari_Qchix_poly_to_sage = lambda f: Qchi_x([pari_Qchi_to_sage(co) for co in f.Vecrev()])
rel_poly = pari_Qchix_poly_to_sage(poly)
rel_degree = rel_poly.degree()
newform['dim'] = dim = chi_degree*rel_degree
small = (dmax==0) or (dim<=dmax)
# for 'small' spaces we compute more data, where 'small' means
# dimension<=dmax (unless dmax==0 in which case all spaces are
# deemed small). However spaces of dimension 1 never need the
# additional data.
if Detail:
print("{}: degree = {} = {}*{}".format(Nko, dim, chi_degree, rel_degree))
if Detail>1:
print("rel_poly for {} is {}".format(Nko,rel_poly))
# The newform has its 'traces' field set already. We will have set
# its 'ans' field where relevant (1<dim<=dmax) to a list of lists
# of coefficients in Qchi. In the genuinely relative case we'll
# need to absolutise these.
if Detail>1: print("raw ans = {}".format(ans))
# dimension 1 spaces: little to do
if dim==1: # e.g. (11,2,1)[0]
newform['poly'] = Qx.gen()
return newform
# no more data required for non-small spaces:
if not small:
return newform
if chi_degree==1: # e.g. (13,4,1)[1]; now rel_degree>1
# field is not relative, ans are lists of integers, coeffs w.r.t. basis
t0=time.time()
ancs = [gen_to_sage(an) for an in ans]
t1 = time.time()
if Detail:
print("time for converting an coeffs to QQ = {}".format(t1-t0))
if Detail>1:
print("Coefficient vectors of ans: {}".format(ancs))
newform['poly'] = rel_poly
# basis is a pari matrix over Q
basis = gen_to_sage(basis)
newform['eigdata'] = {
'pol': rel_poly.list(),
'bas': basis,
'n': 0, # temporary
'm': 0, # temporary
'ancs': ancs,
}
return newform
if rel_degree==1: # e.g. (25,2,4)[0]; now chi_degree>1
# the field is not relative; ans is a lists of 1-lists of
# t_POLMOD modulo a GP poly in t, so we lift them and take
# their coefficient vector to get the coordinates w.r.t. a
# power basis for the field defined by chipoly.
t0=time.time()
ancs = [gen_to_sage(an[0].lift().Vecrev(-dim)) for an in ans] # list of lists of integers/rationals
t1 = time.time()
if Detail:
print("time for converting an coeffs to QQ = {}".format(t1-t0))
if Detail>1:
print("Coefficient vectors of ans: {}".format(ancs))
newform['poly'] = chipoly
# basis is a 1x1 gp matrix over Qchi, so we want the power basis for Qchi: trivial
basis = [[int(i==j) for j in range(dim)] for i in range(dim)]
newform['eigdata'] = {
'pol': chipoly.list(),
'bas': basis,
'n': 0, # temporary
'm': 0, # temporary
'ancs': ancs,
}
return newform
# Now we are in the genuinely relative case where chi_degree>1 and rel_degree>1
# e.g. (25,2,5)
# Now ans is a (python) list of nan (GP) lists of d_rel elements
# of Qchi, and basis is a GP d_rel x d_rel matrix over Qchi
#Setting the Hecke field as a relative extension of Qchi and as an absolute field:
t0=time.time()
Frel = Qchi.extension(rel_poly,'b')
newform['poly'] = abs_poly = Frel.absolute_polynomial()(Qx.gen())
t1 = time.time()
if Detail:
print("absolute poly = {}".format(abs_poly))
if Detail>1:
print("Frel = {}".format(Frel))
print("Time to construct Frel and find absolute poly = {}".format(t1-t0))
Fabs = Frel.absolute_field('a')
if Detail>1:
print("Fabs = {}".format(Fabs))
rel2abs = Fabs.structure()[1] # the isomorphism Frel --> Fabs
z = rel2abs(Qchi.gen())
zpow = [z**i for i in range(chi_degree)]
# convert basis to a Sage list of lists of elements of Qchi:
our_basis_coeffs = [[pari_Qchi_to_sage(basis[i,j]) for j in range(rel_degree)] for i in range(rel_degree)]
#print("our basis coeffs: {} (parent {})".format(our_basis_coeffs, our_basis_coeffs[0][0].parent()))
our_basis_rel = [Frel(b) for b in our_basis_coeffs]
#print("our basis (Sage, relative): {}".format(our_basis_rel))
our_basis_abs = [rel2abs(b) for b in our_basis_rel]
#print("our basis (Sage, absolute): {}".format(our_basis_abs))
basis = sum([[(zpowi*yj).list() for zpowi in zpow] for yj in our_basis_abs],[])
#print("basis (Sage, matrix/Q): {}".format(basis))
t2 = time.time()
if Detail>1:
print("Time to construct Fabs and y,z in Fabs and basis matrix = {}".format(t2-t1))
# Convert coordinates of the an. After lifting these are lists
# of lists of polynomials in Q[t] so simply extracting
# coefficient vectors gives their coordinates in terms of the
# basis z^i*y_j where Qchi=Q(z) and [y_1,...,y_d_rel] is the
# Qchi-basis of Frel. To relate these to the power basis of Fabs
# we only need the change of basis matrix whose rows give the
# power basis coefficients of each z^i*y_j (in the right order).
ancs = [[gen_to_sage(c.lift().Vecrev(-chi_degree)) for c in an] for an in ans]
t4 = time.time()
if Detail>1:
print("Time to construct ancs) = {}".format(t4-t2))
ancs = [sum([anci for anci in anc],[]) for anc in ancs]
if Detail>1:
print("Coefficient vectors of ans: {}".format(ancs))
newform['eigdata'] = {
'pol': abs_poly.list(),
'bas': basis,
'n': 0, # temporary
'm': 0, # temporary
'ancs': ancs,
}
return newform
def process_pari_nf_v1(pari_nf, dmax=20, Detail=0):
r"""
Input is a dict with keys 'Nko' (N,k,chi_number), 'chipoly', 'SB' (Sturm bound), 'pari_newform', 'poly', 'ans', 'ALeigs', 'traces'
Output adds 'traces' (unless already computed), and also 'eigdata' if 1<dimension<=dmax
We do not yet use polredbest or optimize an coeffients
"""
Nko = pari_nf['Nko']
chipoly = pari_nf['chipoly']
poly = pari_nf['poly']
SB = pari_nf['SB']
ALeigs = pari_nf['ALeigs']
traces = pari_nf['traces']
# initialize with data needing no more processing:
newform = {'Nko': Nko, 'chipoly': chipoly, 'SB': SB, 'ALeigs':ALeigs, 'traces':traces}
# Set the polynomial. This is a polynomial in y, (just y if the
# top degree is 1) with coefficients either integers (if the
# bottom degree is 1) or polmods with modulus chipoly. In all
# cases rel_poly will be in Qchi[y].
Qx = PolynomialRing(QQ,'x')
#pari_Qx_poly_to_sage = lambda f: Qx(gen_to_sage(f.Vecrev()))
Qchi = NumberField(chipoly,'t')
chi_degree = chipoly.degree()
# NB the only reason for negating the chi_degree parameter here is to get around a bug in the Sage/pari interface
pari_Qchi_to_sage = lambda elt: Qchi(gen_to_sage(elt.lift().Vecrev(-chi_degree)))
Qchi_x = PolynomialRing(Qchi,'x')
pari_Qchix_poly_to_sage = lambda f: Qchi_x([pari_Qchi_to_sage(co) for co in f.Vecrev()])
rel_poly = pari_Qchix_poly_to_sage(poly)
rel_degree = rel_poly.degree()
newform['dim'] = dim = chi_degree*rel_degree
small = (dmax==0) or (dim<=dmax)
# for 'small' spaces we compute more data, where 'small' means
# dimension<=dmax (unless dmax==0 in which case all spaces are
# deemed small). However spaces of dimension 1 never need the
# additional data.
if Detail:
print("{}: degree = {} = {}*{}".format(Nko, dim, chi_degree, rel_degree))
if Detail>1:
print("rel_poly for {} is {}".format(Nko,rel_poly))
# the newform will have its 'traces' field set already if it is
# the only newform in its (N,k,chi)-newspace. Otherwise we will
# have set its 'ans' field and now compute the traces from that.
# The 'ans' field will be None if we don't need the an, which is
# if (1) the dimension is greater than dmax and (2) the
# (N,k,chi)-newspace is irreducible.
ans = pari_nf['ans']
if Detail>1: print("raw ans = {}".format(ans))
# dimension 1 spaces: special case simpler traces, and no more to do:
x = Qx.gen()
if dim==1: # e.g. (11,2,1)[0]
traces = gen_to_sage(ans)[1:]
if newform['traces']==None:
newform['traces'] = traces
newform['poly'] = x
if Detail>1: print("traces = {}".format(newform['traces']))
return newform
# All dimensions >1: traces
if newform['traces']==None:
traces = [abstrace(an,dim) for an in ans][1:]
# fix up trace(a_1)
traces[0]=dim
if Detail>1: print("traces = {}".format(traces))
newform['traces'] = traces
# no more data required for non-small spaces:
if not small:
return newform
if chi_degree==1 or rel_degree==1: # e.g. (13,4,1)[1] or (25,2,4)[0] respectively
# field is not relative, ans are t_POLMOD modulo pari_pol in y
# or t, so if we lift them and take their coefficient vector
# we'll get the coordinates w.r.t. a power basis for the field
# defined by either rel_poly or chipoly.
t0=time.time()
ancs = [gen_to_sage(an.lift().Vecrev(-dim)) for an in ans][1:]
t1 = time.time()
if Detail:
print("time for converting an coeffs to QQ = {}".format(t1-t0))
basis = [[int(i==j) for j in range(dim)] for i in range(dim)]
newform['poly'] = poly = Qx(rel_poly) if chi_degree==1 else Qx(chipoly)
if Detail>1:
print("Coefficient vectors of ans: {}".format(ancs))
newform['eigdata'] = {
'pol': poly.list(),
'bas': basis,
'n': 0, # temporary
'm': 0, # temporary
'ancs': ancs,
}
return newform
# Now we are in the genuinely relative case where chi_degree>1 and rel_degree>1
# e.g. (25,2,5)
#Setting the Hecke field as a relative extension of Qchi and as an absolute field:
t0=time.time()
Frel = Qchi.extension(rel_poly,'b')
abs_poly = Frel.absolute_polynomial()
newform['poly'] = abs_poly(x)
t1 = time.time()
if Detail:
print("absolute poly = {}".format(abs_poly))
if Detail>1:
print("Time to construct Frel and find absolute poly = {}".format(t1-t0))
Fabs = Frel.absolute_field('a')
rel2abs = Fabs.structure()[1] # the isomorphism Frel --> Fabs
z = rel2abs(Qchi.gen())
y = rel2abs(Frel.gen())
zpow = [z**i for i in range(chi_degree)]
ypow = [y**j for j in range(rel_degree)]
basis = sum([[(zpowi*ypowj).list() for zpowi in zpow] for ypowj in ypow],[])
t2 = time.time()
if Detail>1:
print("Time to construct Fabs and y,z in Fabs and basis matrix = {}".format(t2-t1))
# Get coordinates of the an. After lifting twice these are
# polynomials in Q[t][y] so simply extracting coefficient vectors
# gives their coordinates in terms of the basis z^i*y^j where
# Qchi=Q(z) and F=Qchi(y). To relate these to the power basis of
# Fabs we only need the change of basis matrix whose rows give
# the power basis coefficients of each z^i*y^j (in the right
# order).
ancs = [[gen_to_sage(c.lift().Vecrev(-chi_degree)) for c in a.lift().Vecrev(-rel_degree)] for a in ans][1:]
t4 = time.time()
if Detail>1:
print("Time to construct ancs) = {}".format(t4-t2))
ancs = [sum([anci for anci in anc],[]) for anc in ancs]
if Detail>1:
print("Coefficient vectors of ans: {}".format(ancs))
newform['eigdata'] = {
'pol': abs_poly.list(),
'bas': basis,
'n': 0, # temporary
'm': 0, # temporary
'ancs': ancs,
}
return newform
def bestify_newform(nf, dmax=20, Detail=0):
r"""
Input is a dict with keys
{
'Nko', 'chipoly', 'SB', 'ALeigs', 'dim', 'traces', 'poly', 'eigdata'
}
(with ALeigs only when o=1 and eigdata only when 1<dim<=dmax)
Here eigdata is a dict of the form
'pol': list of d+1 coefficients of polynomial defining Hecke field F
'bas': list of lists of rationals holding dxd matrix whose rows define a Q-basis for F in terms of the power basis
'ancs': list of lists of d rationals giving coefficients of each a_n w.r.t. that basis
'n': 0 (not yet set)
'm': 0 (not yet set)
Output adds 'best_poly' and applies a change of basis to eigdata
changing 'pol' and 'bas' so the basis coefficients are w.r.t. the
best_poly power basis and not the poly power basis.
"""
Nko = nf['Nko']
dim = nf['dim']
if dim==1:
nf['best_poly'] = nf['poly']
return nf
if dim>dmax and dmax>0:
nf['best_poly'] = None
return nf
Qx = PolynomialRing(QQ,'x')
pari_Qx_poly_to_sage = lambda f: Qx(gen_to_sage(f.Vecrev()))
poly = Qx(nf['poly'].list())
if Detail:
print("non-best poly for {} is {}".format(Nko,poly))
nf['best_poly'] = best_poly = polredbest_stable(poly)
if Detail:
print("best_poly for {} is {}".format(Nko,best_poly))
# Now the real work starts
Fold = NumberField(poly,'a')
#print("Fold = {}".format(Fold))
Fnew = NumberField(best_poly,'b')
#print("Fnew = {}".format(Fnew))
iso = Fold.is_isomorphic(Fnew,isomorphism_maps=True)[1][0] # we do not care which isomorphism
#print("iso = {}".format(iso))
iso = pari_Qx_poly_to_sage(iso)
iso = Fold.hom([iso(Fnew.gen())])
new_basis_matrix = [a.list() for a in [iso(b) for b in [Fold(co) for co in nf['eigdata']['bas']]]]
nf['eigdata']['bas'] = new_basis_matrix
nf['eigdata']['pol'] = best_poly.list()
return nf
def integralify_newform(nf, dmax=20, Detail=0):
r"""
Input is a dict with keys
{
'Nko', 'chipoly', 'SB', 'ALeigs', 'dim', 'traces', 'poly', 'best_poly', 'eigdata'
}
(with ALeigs only when o=1 and eigdata only when 1<dim<=dmax)
Output changes eigdata['bas'] and eigdata['ancs'] so the latter are all integral and small, when 1<dim<=dmax.
"""
dim = nf['dim']
if 1<dim and (dim<=dmax or dmax==0):
nf['eigdata'] = eigdata_reduce(nf['eigdata'], nf['SB'], Detail)
return nf
def eigdata_reduce(eigdata, SB, Detail=0):
newcoeffs, newbasis = coeff_reduce(eigdata['ancs'], eigdata['bas'], SB, Detail)
eigdata['bas'] = newbasis
eigdata['ancs'] = newcoeffs
return eigdata
def coeff_reduce(C, B, SB, Detail=0, debug=False):
"""B is a list of lists of rationals holding an invertible dxd matrix
C is a list of lists of rationals holding an nxd matrix
C*B remains invariant
SB = Sturm bound: the first SB rows of C should span the whole row space over Z
Computes invertible U and returns (C', B') = (C*U, U^(-1)*B), so
that the entries of C' are integers and 'small'
"""
t0 = time.time()
if Detail>1:
print("Before LLL, coefficients:\n{}".format(C))
# convert to actual matrices:
d = len(B)
nan = len(C)
B=Matrix(B)
C=Matrix(C)