Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
241852 views
1
#################################################################################
2
#
3
# (c) Copyright 2010 William Stein
4
#
5
# This file is part of PSAGE
6
#
7
# PSAGE is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# PSAGE is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#
20
#################################################################################
21
22
23
"""
24
Toy Implementation of Hilbert Modular Forms
25
26
This file contains an incredibly slow naive toy implementation of
27
Dembele's quaternion algebra algorithm for computing Hilbert modular
28
forms of weight (2,2) and ramified or split prime level. This is for
29
testing and educational purposes only. The file sqrt5_fast.pyx
30
contains a dramatically faster version. That said, figuring out the
31
content of this file based on the contents of Dembele's paper
32
"Explicit Computations of Hilbert Modular Forms on Q(sqrt(5))"
33
was a timing consuming and very painful task.
34
35
EXAMPLES:
36
37
LEVEL 31::
38
39
sage: from psage.modform.hilbert.sqrt5.sqrt5 import THETA, hecke_ops, F
40
sage: B.<i,j,k> = QuaternionAlgebra(F,-1,-1)
41
sage: c = F.factor(31)[1][0]
42
sage: P = F.primes_above(5)[0]
43
sage: TH = THETA(20) # about 1 minute
44
pi = [...]
45
Sorting through 22440 elements
46
sage: T = hecke_ops(c, TH); T # random output do to choice of basis
47
[(5, a + 2, [1 5]
48
[3 3]), (9, 3, [5 5]
49
[3 7]), (11, a + 3, [ 2 10]
50
[ 6 6]), (11, 2*a + 3, [7 5]
51
[3 9]), (19, a + 4, [10 10]
52
[ 6 14]), (19, 3*a + 4, [ 5 15]
53
[ 9 11])]
54
sage: for nm,p,t in T:
55
... print nm, p, t.charpoly().factor()
56
5 a + 2 (x - 6) * (x + 2)
57
9 3 (x - 10) * (x - 2)
58
11 a + 3 (x - 12) * (x + 4)
59
11 2*a + 3 (x - 12) * (x - 4)
60
19 a + 4 (x - 20) * (x - 4)
61
19 3*a + 4 (x - 20) * (x + 4)
62
63
LEVEL 41::
64
65
sage: from psage.modform.hilbert.sqrt5.sqrt5 import THETA, hecke_ops, F
66
sage: B.<i,j,k> = QuaternionAlgebra(F,-1,-1)
67
sage: F.primes_above(41)
68
[Fractional ideal (a - 7), Fractional ideal (a + 6)]
69
sage: c = F.primes_above(41)[0]
70
sage: TH = THETA(11) # about 30 seconds
71
pi = [...]
72
Sorting through 6660 elements
73
sage: T = hecke_ops(c, TH); T # random output do to choice of basis
74
[(5, a + 2, [4 2]
75
[5 1]), (9, 3, [ 6 4]
76
[10 0]), (11, a + 3, [10 2]
77
[ 5 7]), (11, 2*a + 3, [ 8 4]
78
[10 2])]
79
sage: for nm,p,t in T:
80
... print nm, p, t.charpoly().factor()
81
5 a + 2 (x - 6) * (x + 1)
82
9 3 (x - 10) * (x + 4)
83
11 a + 3 (x - 12) * (x - 5)
84
11 2*a + 3 (x - 12) * (x + 2)
85
86
87
LEVEL 389!:
88
89
This relies on having TH from above (say from the level 31 block above)::
90
91
sage: F.primes_above(389)
92
[Fractional ideal (18*a - 5), Fractional ideal (-18*a + 13)]
93
sage: c = F.primes_above(389)[0]
94
sage: T = hecke_ops(c, TH)
95
sage: for nm,p,t in T:
96
... print nm, p, t.charpoly().factor()
97
5 a + 2 (x - 6) * (x^2 + 4*x - 1) * (x^2 - x - 4)^2
98
9 3 (x - 10) * (x^2 + 3*x - 9) * (x^4 - 5*x^3 + 3*x^2 + 6*x - 4)
99
11 a + 3 (x - 12) * (x + 3)^2 * (x^4 - 17*x^2 + 68)
100
11 2*a + 3 (x - 12) * (x^2 + 5*x + 5) * (x^4 - x^3 - 23*x^2 + 18*x + 52)
101
"""
102
103
104
from sage.all import NumberField, polygen, QQ, ZZ, QuaternionAlgebra, cached_function, disk_cached_function
105
106
x = polygen(QQ,'x')
107
F = NumberField(x**2 - x -1, 'a')
108
O_F = F.ring_of_integers()
109
B = QuaternionAlgebra(F,-1,-1,'i,j,k')
110
111
def modp_splitting(p):
112
"""
113
INPUT:
114
115
- p -- ideal of the number field K = B.base() with ring O of integers.
116
117
OUTPUT:
118
119
- matrices I, J in M_2(O/p) such that i |--> I and j |--> J defines
120
an algebra morphism, i.e., I^2=a, J^2=b, I*J=-J*I.
121
122
EXAMPLES::
123
124
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, B, modp_splitting
125
sage: c = F.factor(31)[0][0]
126
sage: modp_splitting(c)
127
(
128
[ 0 30] [18 4]
129
[ 1 0], [ 4 13]
130
)
131
sage: c = F.factor(37)[0][0]; c
132
Fractional ideal (37)
133
sage: I, J = modp_splitting(c); I, J
134
(
135
[ 0 36] [23*abar + 21 36*abar + 8]
136
[ 1 0], [ 36*abar + 8 14*abar + 16]
137
)
138
sage: I^2
139
[36 0]
140
[ 0 36]
141
sage: J^2
142
[36 0]
143
[ 0 36]
144
sage: I*J == -J*I
145
True
146
147
AUTHOR: William Stein
148
"""
149
global B, F
150
151
# Inspired by the code in the function
152
# modp_splitting_data in algebras/quatalg/quaternion_algebra.py
153
if p.number_field() != B.base():
154
raise ValueError, "p must be a prime ideal in the base field of the quaternion algebra"
155
if not p.is_prime():
156
raise ValueError, "p must be prime"
157
158
if F is not p.number_field():
159
raise ValueError, "p must be a prime of %s"%F
160
161
k = p.residue_field()
162
163
from sage.all import MatrixSpace
164
M = MatrixSpace(k, 2)
165
i2, j2 = B.invariants()
166
i2 = k(i2); j2 = k(j2)
167
if k.characteristic() == 2:
168
if i2 == 0 or j2 == 0:
169
raise NotImplementedError
170
return M([0,1,1,0]), M([1,1,0,1])
171
# Find I -- just write it down
172
I = M([0,i2,1,0])
173
# Find J -- I figured this out by just writing out the general case
174
# and seeing what the parameters have to satisfy
175
i2inv = 1/i2
176
a = None
177
for b in list(k):
178
if not b: continue
179
c = j2 + i2inv * b*b
180
if c.is_square():
181
a = -c.sqrt()
182
break
183
if a is None:
184
# do a fallback search; needed in char 3 sometimes.
185
for J in M:
186
K = I*J
187
if J*J == j2 and K == -J*I:
188
return I, J, K
189
190
J = M([a,b,(j2-a*a)/b, -a])
191
K = I*J
192
assert K == -J*I, "bug in that I,J don't skew commute"
193
return I, J
194
195
def modp_splitting_map(p):
196
"""
197
Return a map from subset of B to 2x2 matrix space isomorphic
198
to R tensor OF/p.
199
200
INPUT:
201
- `B` -- quaternion algebra over F=Q(sqrt(5))
202
with invariants -1, -1.
203
- `p` -- prime ideal of F=Q(sqrt(5))
204
205
EXAMPLES::
206
207
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, B, modp_splitting_map
208
sage: i,j,k = B.gens()
209
sage: theta = modp_splitting_map(F.primes_above(5)[0])
210
sage: theta(i + j - k)
211
[2 1]
212
[3 3]
213
sage: s = 2 + 3*i - 2*j - 2*k
214
sage: theta(s)
215
[1 3]
216
[4 3]
217
sage: s.reduced_characteristic_polynomial()
218
x^2 - 4*x + 21
219
sage: theta(s).charpoly()
220
x^2 + x + 1
221
sage: s.reduced_characteristic_polynomial().change_ring(GF(5))
222
x^2 + x + 1
223
sage: theta = modp_splitting_map(F.primes_above(3)[0])
224
sage: smod = theta(s); smod
225
[2*abar + 1 abar + 1]
226
[ abar + 1 abar]
227
sage: smod^2 - 4*smod + 21
228
[0 0]
229
[0 0]
230
"""
231
I, J = modp_splitting(p)
232
F = p.residue_field()
233
def f(x):
234
return F(x[0]) + I*F(x[1]) + J*F(x[2]) + I*J*F(x[3])
235
return f
236
237
def icosian_gens():
238
"""
239
Return generators of the icosian group, as elements of the
240
Hamilton quaternion algebra B over Q(sqrt(5)).
241
242
AUTHOR: William Stein
243
244
EXAMPLES::
245
246
sage: from psage.modform.hilbert.sqrt5.sqrt5 import icosian_gens
247
sage: icosian_gens()
248
[i, j, k, -1/2 + 1/2*i + 1/2*j + 1/2*k, 1/2*i + 1/2*a*j + (-1/2*a + 1/2)*k]
249
sage: [a.reduced_norm() for a in icosian_gens()]
250
[1, 1, 1, 1, 1]
251
"""
252
global B, F
253
254
omega = F.gen() # (1+sqrt(5))/2
255
omega_bar = 1 - F.gen() # (1-sqrt(5))/2 = 1 - (1+sqrt(5))/2
256
return [B(v)/2 for v in [(0,2,0,0), (0,0,2,0),
257
(0,0,0,2), (-1,1,1,1), (0,1,omega,omega_bar)]]
258
259
260
261
def compute_all_icosians():
262
"""
263
Return a list of the elements of the Icosian group of order 120,
264
which we compute by generating enough products of icosian
265
generators.
266
267
EXAMPLES::
268
269
sage: from psage.modform.hilbert.sqrt5.sqrt5 import compute_all_icosians, all_icosians
270
sage: v = compute_all_icosians()
271
sage: len(v)
272
120
273
sage: v
274
[1/2 + 1/2*a*i + (-1/2*a + 1/2)*k, 1/2 + (-1/2*a + 1/2)*i + 1/2*a*j,..., -k, i, j, -i]
275
sage: assert set(v) == set(all_icosians()) # double check
276
"""
277
from sage.all import permutations, cartesian_product_iterator
278
Icos = []
279
ig = icosian_gens()
280
per = permutations(range(5))
281
exp = cartesian_product_iterator([range(1,i) for i in [5,5,5,4,5]])
282
for f in exp:
283
for p in per:
284
e0 = ig[p[0]]**f[0]
285
e1 = ig[p[1]]**f[1]
286
e2 = ig[p[2]]**f[2]
287
e3 = ig[p[3]]**f[3]
288
e4 = ig[p[4]]**f[4]
289
elt = e0*e1*e2*e3*e4
290
if elt not in Icos:
291
Icos.append(elt)
292
if len(Icos) == 120:
293
return Icos
294
295
@cached_function
296
def all_icosians():
297
"""
298
Return a list of all 120 icosians, from a precomputed table.
299
300
EXAMPLES::
301
302
sage: from psage.modform.hilbert.sqrt5.sqrt5 import all_icosians
303
sage: v = all_icosians()
304
sage: len(v)
305
120
306
"""
307
s = '[1+a*i+(-a+1)*k,1+(-a+1)*i+a*j,-a+i+(a-1)*j,1+(-a)*i+(-a+1)*k,-a-j+(-a+1)*k,1+(a-1)*i+(-a)*j,-1+(-a)*i+(a-1)*k,-1+(a-1)*i+(-a)*j,-a+1+(-a)*j-k,-1+a*i+(-a+1)*k,-a+1+i+(-a)*k,-1+(-a+1)*i+(-a)*j,(-a+1)*i+j+(-a)*k,a-1+(-a)*j+k,(a-1)*i-j+a*k,a+i+(-a+1)*j,1+a*i+(a-1)*k,-1+(-a)*i+(-a+1)*k,a*i+(-a+1)*j-k,a-1+i+a*k,(-a)*i+(a-1)*j+k,a+j+(-a+1)*k,1+(-a+1)*i+(-a)*j,-1+(a-1)*i+a*j,a-i+(-a+1)*j,-1+a*i+(a-1)*k,a+j+(a-1)*k,-1+(-a+1)*i+a*j,(-a+1)*i+j+a*k,a-1+a*j+k,-a+i+(-a+1)*j,1+(-a)*i+(a-1)*k,a*i+(-a+1)*j+k,a-1-i+a*k,-a+j+(a-1)*k,1+(a-1)*i+a*j,(a-1)*i+j+a*k,-a+1+a*j+k,(-a)*i+(-a+1)*j+k,-a+1+i+a*k,-a-i+(-a+1)*j,-a+1+(-a)*j+k,(-a+1)*i-j+a*k,(a-1)*i+j+(-a)*k,a+i+(a-1)*j,a-1+a*j-k,-a+j+(-a+1)*k,-a+1-i+a*k,a*i+(a-1)*j+k,(-a)*i+(-a+1)*j-k,a-j+(a-1)*k,a-1+i+(-a)*k,-1+i+j+k,-1-i-j+k,1-i-j-k,1+i-j+k,a-j+(-a+1)*k,-1+i-j-k,1-i+j+k,(a-1)*i-j+(-a)*k,-a-j+(a-1)*k,1+i+j-k,a*i+(a-1)*j-k,-1-i+j-k,a-1+(-a)*j-k,-a+1+a*j-k,(-a)*i+(a-1)*j-k,-a+1-i+(-a)*k,-a-i+(a-1)*j,a-1-i+(-a)*k,(-a+1)*i-j+(-a)*k,a-i+(a-1)*j,-i+(-a)*j+(a-1)*k,i+a*j+(a-1)*k,i+a*j+(-a+1)*k,-i+a*j+(a-1)*k,-i+a*j+(-a+1)*k,i+(-a)*j+(a-1)*k,-i+(-a)*j+(-a+1)*k,i+(-a)*j+(-a+1)*k,-1-i-j-k,1+i+j+k,2,-a+1+a*i-j,-2,-a+(-a+1)*i-k,a-1+a*i+j,a+(-a+1)*i+k,a-1+(-a)*i+j,1+(-a+1)*j+(-a)*k,-a+1+a*i+j,-1+(-a+1)*j+a*k,a+(a-1)*i+k,-1+(a-1)*j+a*k,-a+(-a+1)*i+k,1+(-a+1)*j+a*k,-a+1+(-a)*i+j,-a+(a-1)*i+k,a-1+a*i-j,1+(a-1)*j+a*k,a+(-a+1)*i-k,-1+(-a+1)*j+(-a)*k,-a+1+(-a)*i-j,-a+(a-1)*i-k,a-1+(-a)*i-j,1+(a-1)*j+(-a)*k,a+(a-1)*i-k,-1+(a-1)*j+(-a)*k,-1+i+j-k,1-i+j-k,1-i-j+k,-1-i+j+k,-1+i-j+k,1+i-j-k,2*k,(-2)*j,(-2)*k,2*i,2*j,(-2)*i]'
308
v = eval(s, {'a':F.gen(), 'i':B.gen(0), 'j':B.gen(1), 'k':B.gen(0)*B.gen(1)})
309
return [B(x)/B(2) for x in v]
310
311
312
def icosian_ring_gens():
313
"""
314
Return ring generators for the icosian ring (a maximal order) in the
315
quaternion algebra ramified only at infinity over F=Q(sqrt(5)).
316
These are generators over the ring of integers of F.
317
318
OUTPUT:
319
320
- list
321
322
EXAMPLES::
323
324
sage: from psage.modform.hilbert.sqrt5.sqrt5 import icosian_ring_gens
325
sage: icosian_ring_gens()
326
[1/2 + (1/2*a - 1/2)*i + 1/2*a*j, (1/2*a - 1/2)*i + 1/2*j + 1/2*a*k, 1/2*a*i + (1/2*a - 1/2)*j + 1/2*k, 1/2*i + 1/2*a*j + (1/2*a - 1/2)*k]
327
"""
328
global B, F
329
# See page 6 of Dembele.
330
# DO NOT CHANGE THESE! You'll break, e.g., the optimized code for
331
# writing elements in terms of these...
332
omega = F.gen()
333
omega_bar = 1 - F.gen()
334
return [B(v)/2 for v in [(1,-omega_bar,omega,0),
335
(0,-omega_bar,1,omega),
336
(0,omega,-omega_bar,1),
337
(0,1,omega,-omega_bar)]]
338
339
def icosian_ring_gens_over_ZZ():
340
"""
341
Return basis over ZZ for the icosian ring, which has ZZ-rank 8.
342
343
EXAMPLES::
344
345
sage: from psage.modform.hilbert.sqrt5.sqrt5 import icosian_ring_gens_over_ZZ
346
sage: v = icosian_ring_gens_over_ZZ(); v
347
[1/2 + (1/2*a - 1/2)*i + 1/2*a*j, (1/2*a - 1/2)*i + 1/2*j + 1/2*a*k, 1/2*a*i + (1/2*a - 1/2)*j + 1/2*k, 1/2*i + 1/2*a*j + (1/2*a - 1/2)*k, 1/2*a + 1/2*i + (1/2*a + 1/2)*j, 1/2*i + 1/2*a*j + (1/2*a + 1/2)*k, (1/2*a + 1/2)*i + 1/2*j + 1/2*a*k, 1/2*a*i + (1/2*a + 1/2)*j + 1/2*k]
348
sage: len(v)
349
8
350
"""
351
global B, F
352
I = icosian_ring_gens()
353
omega = F.gen()
354
return I + [omega*x for x in I]
355
356
def tensor_over_QQ_with_RR(prec=53):
357
"""
358
Return map from the quaternion algebra B to the tensor product of
359
B over QQ with RealField(prec), viewed as an 8-dimensional real
360
vector space.
361
362
INPUT:
363
- prec -- (integer: default 53); bits of real precision
364
365
OUTPUT:
366
- a Python function
367
368
EXAMPLES::
369
370
sage: from psage.modform.hilbert.sqrt5.sqrt5 import tensor_over_QQ_with_RR, B
371
sage: f = tensor_over_QQ_with_RR()
372
sage: B.gens()
373
[i, j, k]
374
sage: f(B.0)
375
(0.000000000000000, 1.00000000000000, 0.000000000000000, 0.000000000000000, 0.000000000000000, 1.00000000000000, 0.000000000000000, 0.000000000000000)
376
sage: f(B.1)
377
(0.000000000000000, 0.000000000000000, 1.00000000000000, 0.000000000000000, 0.000000000000000, 0.000000000000000, 1.00000000000000, 0.000000000000000)
378
sage: f = tensor_over_QQ_with_RR(20)
379
sage: f(B.0 - (1/9)*B.1)
380
(0.00000, 1.0000, -0.11111, 0.00000, 0.00000, 1.0000, -0.11111, 0.00000)
381
"""
382
global B, F
383
from sage.all import RealField
384
RR = RealField(prec=prec)
385
V = RR**8
386
S = F.embeddings(RR)
387
def f(x):
388
return V(sum([[sigma(a) for a in x] for sigma in S],[]))
389
return f
390
391
def modp_icosians(p):
392
"""
393
Return matrices of images of all 120 icosians modulo p.
394
395
INPUT:
396
- p -- *split* or ramified prime ideal of real quadratic field F=Q(sqrt(5))
397
398
OUTPUT:
399
- list of matrices modulo p.
400
401
EXAMPLES::
402
403
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, modp_icosians
404
sage: len(modp_icosians(F.primes_above(5)[0]))
405
120
406
sage: v = modp_icosians(F.primes_above(11)[0])
407
sage: len(v)
408
120
409
sage: v[0]
410
[10 8]
411
[ 8 1]
412
sage: v[-1]
413
[0 3]
414
[7 0]
415
"""
416
I, J = modp_splitting(p); K = I*J
417
k = p.residue_field()
418
G = [k(g[0]) + k(g[1])*I + k(g[2])*J + k(g[3])*K for g in icosian_gens()]
419
from sage.all import MatrixGroup
420
return [g.matrix() for g in MatrixGroup(G)]
421
422
class P1ModList(object):
423
"""
424
Object the represents the elements of the projective line modulo
425
a nonzero *prime* ideal of the ring of integers of Q(sqrt(5)).
426
427
Elements of the projective line are represented by elements of a 2-dimension
428
vector space over the residue field.
429
430
EXAMPLES::
431
432
We construct the projective line modulo the ideal (2), and illustrate
433
all the standard operations with it::
434
435
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, P1ModList
436
sage: P1 = P1ModList(F.primes_above(2)[0]); P1
437
Projective line over Residue field in abar of Fractional ideal (2)
438
sage: len(P1)
439
5
440
sage: list(P1)
441
[(0, 1), (1, 0), (1, abar), (1, abar + 1), (1, 1)]
442
sage: P1.random_element() # random output
443
(1, abar + 1)
444
sage: z = P1.random_element(); z # random output
445
(1, 0)
446
sage: z[0].parent()
447
Residue field in abar of Fractional ideal (2)
448
sage: g = z[0].parent().gen()
449
sage: P1.normalize((g,g))
450
(1, 1)
451
sage: P1((g,g))
452
(1, 1)
453
"""
454
def __init__(self, c):
455
"""
456
INPUT:
457
- c -- a nonzero prime of the ring of integers of Q(sqrt(5))
458
459
EXAMPLES::
460
461
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, P1ModList
462
sage: P1ModList(F.primes_above(3)[0])
463
Projective line over Residue field in abar of Fractional ideal (3)
464
sage: P1ModList(F.primes_above(11)[1])
465
Projective line over Residue field of Fractional ideal (3*a - 1)
466
sage: list(P1ModList(F.primes_above(5)[0]))
467
[(0, 1), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4)]
468
"""
469
self._c = c
470
F = c.residue_field()
471
V = F**2
472
self._V = V
473
self._F = F
474
self._list = [ V([0,1]) ] + [V([1,a]) for a in F]
475
for a in self._list:
476
a.set_immutable()
477
478
def random_element(self):
479
"""
480
Return a random element of this projective line.
481
482
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, P1ModList
483
sage: P1 = P1ModList(F.primes_above(13)[0]); P1
484
Projective line over Residue field in abar of Fractional ideal (13)
485
sage: P1.random_element() # random output
486
(1, 10*abar + 5)
487
"""
488
import random
489
return random.choice(self._list)
490
491
def normalize(self, uv):
492
"""
493
Normalize a representative element so it is either of the form
494
(1,*) if the first entry is nonzero, or of the form (0,1)
495
otherwise.
496
497
EXAMPLES::
498
499
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, P1ModList
500
sage: p = F.primes_above(13)[0]
501
sage: P1 = P1ModList(p)
502
sage: k = p.residue_field()
503
sage: g = k.gen()
504
sage: P1.normalize([3,4])
505
(1, 10)
506
sage: P1.normalize([g,g])
507
(1, 1)
508
sage: P1.normalize([0,g])
509
(0, 1)
510
"""
511
w = self._V(uv)
512
if w[0]:
513
w = (~w[0]) * w
514
w.set_immutable()
515
#assert w in self._list
516
return w
517
else:
518
return self._list[0]
519
520
def __len__(self):
521
"""
522
Return number of elements of this P1.
523
524
EXAMPLES::
525
526
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, P1ModList
527
sage: len(P1ModList(F.primes_above(3)[0]))
528
10
529
sage: len(P1ModList(F.primes_above(5)[0]))
530
6
531
sage: len(P1ModList(F.primes_above(19)[0]))
532
20
533
sage: len(P1ModList(F.primes_above(19)[1]))
534
20
535
"""
536
return len(self._list)
537
538
def __getitem__(self, i):
539
"""
540
Return i-th element.
541
542
EXAMPLES::
543
544
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, P1ModList
545
sage: P = P1ModList(F.primes_above(3)[0]); list(P)
546
[(0, 1), (1, 0), (1, 2*abar), (1, abar + 1), (1, abar + 2), (1, 2), (1, abar), (1, 2*abar + 2), (1, 2*abar + 1), (1, 1)]
547
sage: P[2]
548
(1, 2*abar)
549
"""
550
return self._list[i]
551
552
def __call__(self, x):
553
"""
554
Coerce x into this P1 list. Here x is anything that coerces to
555
the 2-dimensional vector space over the residue field. The
556
result is normalized (in fact this function is just an alias
557
for the normalize function).
558
559
EXAMPLES::
560
561
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, P1ModList
562
sage: p = F.primes_above(13)[0]
563
sage: k = p.residue_field(); g = k.gen()
564
sage: P1 = P1ModList(p)
565
sage: P1([3,4])
566
(1, 10)
567
sage: P1([g,g])
568
(1, 1)
569
sage: P1(P1([g,g]))
570
(1, 1)
571
"""
572
return self.normalize(x)
573
574
def __repr__(self):
575
"""
576
EXAMPLES::
577
578
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, P1ModList
579
sage: P1ModList(F.primes_above(19)[1]).__repr__()
580
'Projective line over Residue field of Fractional ideal (-4*a + 3)'
581
"""
582
return 'Projective line over %s'%self._F
583
584
585
def P1_orbits(p):
586
"""
587
INPUT:
588
- p -- a split or ramified prime of the integers of Q(sqrt(5)).
589
590
OUTPUT:
591
- ``orbits`` -- dictionary mapping elements of P1 to a choice of orbit rep
592
- ``reps`` -- list of representatives for the orbits
593
- ``P1`` -- the P1ModList object
594
595
AUTHOR: William Stein
596
597
EXAMPLES::
598
599
sage: from psage.modform.hilbert.sqrt5.sqrt5 import P1_orbits, F
600
sage: orbits, reps, P1 = P1_orbits(F.primes_above(5)[0])
601
sage: orbits # random output
602
{(1, 2): (1, 0), (0, 1): (1, 0), (1, 3): (1, 0), (1, 4): (1, 0), (1, 0): (1, 0), (1, 1): (1, 0)}
603
sage: reps # random output
604
[(1, 1)]
605
sage: len(reps)
606
1
607
sage: P1
608
Projective line over Residue field of Fractional ideal (2*a - 1)
609
sage: orbits, reps, P1 = P1_orbits(F.primes_above(41)[0])
610
sage: reps # random output
611
[(1, 40), (1, 5)]
612
sage: len(reps)
613
2
614
"""
615
global B
616
617
P1 = P1ModList(p)
618
ICO = modp_icosians(p)
619
620
def act(u, t):
621
return P1(u*t)
622
623
cur = P1.random_element()
624
reps = [cur]
625
orbits = {cur:cur}
626
while len(orbits) < len(P1):
627
for u in ICO:
628
s = act(u, cur)
629
if not orbits.has_key(s):
630
orbits[s] = cur
631
if len(orbits) < len(P1):
632
# choose element of P1 not a key
633
while True:
634
c = P1.random_element()
635
if c not in orbits:
636
cur = c
637
reps.append(cur)
638
orbits[cur] = cur
639
break
640
# done
641
return orbits, reps, P1
642
643
def P1_orbits2(p):
644
"""
645
INPUT:
646
- p -- a split or ramified prime of the integers of Q(sqrt(5)).
647
648
OUTPUT:
649
- list of disjoint sets of elements of P1 that are orbits
650
651
AUTHOR: William Stein
652
653
EXAMPLES::
654
655
sage: from psage.modform.hilbert.sqrt5.sqrt5 import P1_orbits2, F
656
sage: P1_orbits2(F.primes_above(5)[0]) # random output
657
[set([(1, 2), (0, 1), (1, 3), (1, 4), (1, 0), (1, 1)])]
658
sage: P1_orbits2(F.primes_above(11)[0]) # random output
659
[set([(0, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 8), (1, 6), (1, 9), (1, 7), (1, 10), (1, 0), (1, 1)])]
660
sage: len(P1_orbits2(F.primes_above(41)[0]))
661
2
662
"""
663
global B
664
665
P1 = P1ModList(p)
666
ICO = modp_icosians(p)
667
668
orbits = []
669
while sum(len(x) for x in orbits) < len(P1):
670
v = P1.random_element()
671
skip = False
672
for O in orbits:
673
if v in O:
674
skip = True
675
break
676
if skip: continue
677
O = set([P1(g*v) for g in ICO])
678
orbits.append(O)
679
# Now make a dictionary
680
return orbits
681
682
def totally_pos_gen(p):
683
"""
684
Given a prime ideal p of a narrow class number 1 real quadratic
685
field, return a totally positive generator for p.
686
687
INPUT:
688
- p -- prime ideal of narrow class number 1 real
689
quadratic field
690
691
OUTPUT:
692
- generator of p that is totally positive
693
694
AUTHOR: William Stein
695
696
EXAMPLES::
697
698
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, totally_pos_gen
699
sage: g = totally_pos_gen(F.factor(19)[0][0]); g
700
3*a + 4
701
sage: g.norm()
702
19
703
sage: g.complex_embeddings()
704
[2.14589803375032, 8.85410196624968]
705
706
sage: for p in primes(14):
707
... for P, e in F.factor(p):
708
... g = totally_pos_gen(P)
709
... print P, g, g.complex_embeddings()
710
Fractional ideal (2) 2 [2.00000000000000, 2.00000000000000]
711
Fractional ideal (3) 3 [3.00000000000000, 3.00000000000000]
712
Fractional ideal (2*a - 1) a + 2 [1.38196601125011, 3.61803398874989]
713
Fractional ideal (7) 7 [7.00000000000000, 7.00000000000000]
714
Fractional ideal (3*a - 2) a + 3 [2.38196601125011, 4.61803398874989]
715
Fractional ideal (3*a - 1) 2*a + 3 [1.76393202250021, 6.23606797749979]
716
Fractional ideal (13) 13 [13.0000000000000, 13.0000000000000]
717
"""
718
F = p.number_field()
719
assert F.degree() == 2 and F.discriminant() > 0
720
G = p.gens_reduced()
721
if len(G) != 1:
722
raise ValueError, "ideal not principal"
723
g = G[0]
724
725
from sage.all import RR
726
sigma = F.embeddings(RR)
727
728
e = [s(g) for s in sigma]
729
u = F.unit_group().gen(1)
730
ue = [s(u) for s in sigma]
731
if ue[0] > 0 and ue[1] < 0:
732
u *= -1
733
if e[0] < 0 and e[1] < 0:
734
return -g
735
elif e[0] < 0 and e[1] > 0:
736
if ue[0] < 0 and ue[1] > 0:
737
return u*g
738
else:
739
raise ValueError, "no totally positive generator"
740
elif e[0] > 0 and e[1] > 0:
741
return g
742
elif e[0] > 0 and e[1] < 0:
743
if ue[0] < 0 and ue[1] > 0:
744
return -u*g
745
else:
746
raise ValueError, "no totally positive generator"
747
assert False, "bug"
748
749
def gram_matrix_of_maximal_order(R):
750
"""
751
Return 8x8 Gram matrix of maximal order defined by R, which is assumed to be
752
a basis for maximal order over ZZ.
753
754
EXAMPLES::
755
756
sage: from psage.modform.hilbert.sqrt5.sqrt5 import icosian_ring_gens_over_ZZ, gram_matrix_of_maximal_order
757
sage: R = icosian_ring_gens_over_ZZ()
758
sage: gram_matrix_of_maximal_order(R)
759
[4 2 2 1 2 1 1 3]
760
[2 4 1 1 1 2 3 3]
761
[2 1 4 1 1 3 2 3]
762
[1 1 1 4 3 3 3 2]
763
[2 1 1 3 6 3 3 4]
764
[1 2 3 3 3 6 4 4]
765
[1 3 2 3 3 4 6 4]
766
[3 3 3 2 4 4 4 6]
767
"""
768
G = [[(R[i]*R[j].conjugate()).reduced_trace().trace()
769
for i in range(8)] for j in range(8)]
770
from sage.all import matrix
771
return matrix(ZZ, G)
772
773
def qfminim(qf, N):
774
"""
775
Call the PARI qfminim method on qf and 2*N, with smaller and
776
and smaller search range, until a MemoryError is *not* raised.
777
On a large-memory machine this will succeed the first time.
778
779
EXAMPLES::
780
781
sage: from psage.modform.hilbert.sqrt5.sqrt5 import icosian_ring_gens_over_ZZ, gram_matrix_of_maximal_order
782
sage: R = icosian_ring_gens_over_ZZ()
783
sage: G = gram_matrix_of_maximal_order(R)
784
sage: qf = pari(G)
785
sage: from psage.modform.hilbert.sqrt5.sqrt5 import icosian_ring_gens_over_ZZ, gram_matrix_of_maximal_order, qfminim
786
sage: n, m, v = qfminim(qf, 2)
787
sage: n
788
120
789
sage: m
790
4
791
sage: v[0]
792
[0, 0, 0, -1, 1, 0, 0, 0]~
793
"""
794
i = 32
795
while i>10:
796
try:
797
return qf.qfminim(2*N, 2**i)
798
except MemoryError:
799
i -= 1
800
801
802
def bounded_elements(N):
803
"""
804
Return elements in maximal order of B that have reduced norm
805
whose trace is at most N.
806
807
EXAMPLES::
808
809
sage: from psage.modform.hilbert.sqrt5.sqrt5 import bounded_elements
810
sage: X = bounded_elements(3)
811
sage: len(X)
812
180
813
sage: rnX = [a.reduced_norm() for a in X]
814
sage: set([a.trace() for a in rnX])
815
set([2, 3])
816
sage: set([a.norm() for a in rnX])
817
set([1])
818
sage: X = bounded_elements(5)
819
sage: len(X)
820
1200
821
sage: rnX = [a.reduced_norm() for a in X]
822
sage: set([a.trace() for a in rnX])
823
set([2, 3, 4, 5])
824
sage: set([a.norm() for a in rnX])
825
set([1, 4, 5])
826
"""
827
# Get our maximal order
828
R = icosian_ring_gens_over_ZZ()
829
830
# Compute Gram matrix of R
831
G = gram_matrix_of_maximal_order(R)
832
833
# Make PARI quadratic form
834
from sage.all import pari
835
qf = pari(G)
836
837
# Get the vectors of norm up to N.
838
# The 2 is because we had to scale by 2 to get
839
# rid of denominator in Gram matrix.
840
Z = qfminim(qf, N)
841
Z2 = Z[2].sage().transpose()
842
843
# For each vector, make the corresponding element of B.
844
# TODO: This step massively dominates the runtime, and can be
845
# easily made trivial with careful thought.
846
V = []
847
for i in range(Z2.nrows()):
848
w = Z2[i]
849
V.append(sum(w[j]*R[j] for j in range(8)))
850
return V
851
852
from tables import primes_of_bounded_norm
853
854
def THETA(N):
855
r"""
856
Return representative elements of the maximal order of `R` of
857
reduced norm `\pi_p` up to `N` modulo the left action of the units
858
of `R` (the icosians). Here `\pi_p` runs through totally positive
859
generators of the odd prime ideals with norm up to `N`.
860
861
INPUT:
862
- `N` -- a positive integer >= 4.
863
864
OUTPUT:
865
- dictionary with keys the totally positive generators of the
866
odd prime ideals with norm up to and including `N`, and values
867
a dictionary with values the actual elements of reduced norm
868
`\pi_p`.
869
870
AUTHOR: William Stein
871
872
EXAMPLES::
873
874
sage: from psage.modform.hilbert.sqrt5.sqrt5 import THETA
875
sage: d = THETA(9)
876
pi = [2, a + 2, 3]
877
Sorting through 2400 elements
878
sage: d.keys()
879
[a + 2, 3]
880
sage: d[3]
881
{(0, 1): a + 1/2*i + (-1/2*a + 1)*j + (-1/2*a + 1/2)*k, (1, 2): a - 1 + a*j, (1, abar): a - 1/2 + (1/2*a + 1/2)*i + (-1/2*a + 1)*j, (1, abar + 1): a - 1/2 + 1/2*i + 1/2*j + (-a + 1/2)*k, (1, abar + 2): a - 1 + (1/2*a + 1/2)*i + 1/2*j + (-1/2*a)*k, (1, 2*abar + 2): a - 1 + 1/2*a*i + (1/2*a + 1/2)*j + 1/2*k, (1, 2*abar): a - 1/2 + i + (-1/2*a + 1/2)*j + (-1/2*a)*k, (1, 2*abar + 1): a - 1/2 + (-1/2*a)*i + j + (-1/2*a + 1/2)*k, (1, 0): a + (-a + 1)*k, (1, 1): a - 1/2 + 1/2*a*i + j + (-1/2*a + 1/2)*k}
882
sage: k = d[3].keys(); k
883
[(0, 1), (1, 2), (1, abar), (1, abar + 1), (1, abar + 2), (1, 2*abar + 2), (1, 2*abar), (1, 2*abar + 1), (1, 0), (1, 1)]
884
sage: d[3][k[0]]
885
a + 1/2*i + (-1/2*a + 1)*j + (-1/2*a + 1/2)*k
886
sage: d[3][k[0]].reduced_norm()
887
3
888
"""
889
# ** NOTE: This algorithm will not scale up well, because there
890
# are so many vectors of bounded norm.
891
####################################################################
892
# Algorithm:
893
# * Enumerate set S of primes of norm <= N.
894
# * For each prime p in S:
895
# - Find a totally positive generator pi_p for p.
896
# - Compute mod-p local splitting theta_p.
897
# * Compute set X of elements in maximal order of B of norm <= N
898
# using the Gram matrix of the icosian ring (maximal order).
899
# * For each element z of X, compute the reduced norm of z.
900
# If it equals pi_p for one of the pi_p, compute the
901
# top row v of the reduced row echelon form of theta_p(z).
902
# Store v:z if there isn't already something for v.
903
# Also, of course, store this stuff separately for each p.
904
####################################################################
905
global B, F
906
907
# Get primes of norm up to N.
908
S = primes_of_bounded_norm(N)
909
910
# Find totally positive generators pi_p
911
pi = [totally_pos_gen(p) for p in S]
912
print "pi =",pi
913
914
# Compute traces of the generators, since that's what
915
# the bounded_elements command computes up to.
916
tr = [abs(x.trace()) for x in pi]
917
N = max(tr)
918
919
# A list that at least includes all elements (up to -1) whose
920
# reduced norm has trace at most N.
921
X = bounded_elements(N)
922
923
# Compute mod-p local splitting maps
924
theta_map = {}
925
for i, p in enumerate(S):
926
theta_map[pi[i]] = modp_splitting_map(p)
927
928
# Sort through the elements of X.
929
pi_set = set(pi)
930
931
# TODO: We skip the prime 2, since the mod-p splitting map is
932
# broken there.
933
pi_set.remove(2)
934
935
Theta = {}
936
for pi_p in pi_set:
937
Theta[pi_p] = {}
938
939
# The dictionary Theta will have keys the pi_p and
940
# the dictionary for pi_p has keys reduced vectors
941
# in (OF/p)^2. Here "reduced" just means "reduced
942
# row echelon form", so scaled so first entry is 1.
943
print "Sorting through %s elements"%len(X)
944
for a in X:
945
nrm = a.reduced_norm()
946
if nrm in pi_set:
947
# this is: mod right action of R^* acting on the right,
948
# so column echelon form
949
v = theta_map[nrm](a).transpose().echelon_form()[0]
950
951
## for reference, this is: mod left action of R^*,
952
## which is wrong, I guess:
953
# v = theta_map[nrm](a).echelon_form()[0]
954
955
z = Theta[nrm]
956
if z.has_key(v):
957
pass
958
else:
959
z[v] = a
960
961
return Theta
962
963
def hecke_ops(c, X):
964
orbits, reps, P1 = P1_orbits(c)
965
theta_c = modp_splitting_map(c)
966
def Tp(pi):
967
z = X[pi]
968
mat = []
969
for x in reps:
970
row = [0]*len(reps)
971
for _, w in z.iteritems():
972
w_c = theta_c(w)
973
y = w_c**(-1) * x
974
y_red = orbits[P1.normalize(y)]
975
row[reps.index(y_red)] += 1
976
mat.append(row)
977
from sage.all import matrix
978
return matrix(ZZ, mat)
979
ans = [(pi.norm(), pi, Tp(pi)) for pi in X.keys()]
980
ans.sort()
981
return ans
982
983
984
def hecke_ops2(c, X):
985
reduce, reps, P1 = P1_orbits(c)
986
theta_c = modp_splitting_map(c)
987
def Tp(pi):
988
z = X[pi]
989
mat = []
990
for x in reps:
991
print "x = %s, card = %s"%(x, len([M for M in reduce.keys() if reduce[M]==x]))
992
row = [0]*len(reps)
993
for _, w in z.iteritems():
994
w_c = theta_c(w)
995
y = w_c**(-1) * x
996
print "y =", y
997
y_red = reduce[P1(y)]
998
row[reps.index(y_red)] += 1
999
print "y_red =", y_red
1000
mat.append(row)
1001
from sage.all import matrix
1002
return matrix(ZZ, mat)
1003
ans = [(pi.norm(), pi, Tp(pi)) for pi in X.keys()]
1004
ans.sort()
1005
return ans
1006
1007
class AlphaZ:
1008
def __init__(self, P):
1009
"""
1010
Computing elements with norm pi, where P=(pi).
1011
1012
INPUT:
1013
- P - a prime of O_F
1014
1015
OUTPUT:
1016
- element alpha in B with norm pi
1017
whose image via the mod-p splitting map
1018
has column echelon form with first column z
1019
"""
1020
self.R_Z = icosian_ring_gens_over_ZZ()
1021
self.P = P
1022
self.p = P.smallest_integer()
1023
self.pi = totally_pos_gen(P)
1024
self.deg = P.residue_class_degree()
1025
f = modp_splitting_map(self.P)
1026
self.n = [f(g) for g in self.R_Z]
1027
1028
def local_map(self):
1029
n = self.n
1030
if self.deg == 1:
1031
k = n[0].parent().base_ring()
1032
W = k**4
1033
V = k**8
1034
return V.hom([W(x.list()) for x in n])
1035
else:
1036
# P is an inert prime
1037
from sage.all import GF
1038
k = GF(self.p)
1039
W = k**8
1040
V = k**8
1041
return V.hom([W(sum([y._vector_().list() for y in x.list()],[])) for x in n])
1042
1043
def ideal_mod_p(self, z):
1044
"""
1045
INPUT:
1046
- z - an element of P^1(O_F/p).
1047
"""
1048
A = self.local_map()
1049
phi = self.local_map()
1050
V = phi.codomain()
1051
if self.deg == 1:
1052
g0 = V([z[0],0,z[1],0])
1053
g1 = V([0,z[0],0,z[1]])
1054
W = V.span([g0,g1])
1055
else:
1056
n = self.n
1057
M2 = n[0].parent()
1058
a = M2.base_ring().gen()
1059
g0 = M2([z[0],0,z[1],0])
1060
g1 = a*g0
1061
g2 = M2([0,z[0],0,z[1]])
1062
g3 = a*g2
1063
z = [g0,g1,g2,g3]
1064
W = V.span([V(sum([y._vector_().list() for y in x.list()],[])) for x in z])
1065
return phi.inverse_image(W)
1066
1067
def ideal(self, z):
1068
Imod = self.ideal_mod_p(z)
1069
A = Imod.basis_matrix().lift()
1070
from sage.all import identity_matrix
1071
p = self.p
1072
B = A.stack(p*identity_matrix(ZZ,8))
1073
V = B.row_module()
1074
return V
1075
1076
def ideal_basis(self, z):
1077
J = self.ideal(z)
1078
R = self.R_Z
1079
return [sum(g[i]*R[i] for i in range(8)) for g in J.gens()]
1080
1081
def ideal_gram(self, W):
1082
G = [[(W[i]*W[j].conjugate()).reduced_trace().trace()
1083
for i in range(8)] for j in range(8)]
1084
from sage.all import matrix
1085
return matrix(ZZ, G)
1086
1087
def alpha(self, z):
1088
"""
1089
INPUT:
1090
- z - an element of P^1(O_F/P).
1091
"""
1092
W = self.ideal_basis(z)
1093
G = self.ideal_gram(W)
1094
from sage.all import pari
1095
qf = pari(G)
1096
t = self.pi.trace()
1097
c = qfminim(qf, t)
1098
#print "number of vectors", c[0]
1099
for r in c[2].sage().transpose():
1100
a = sum([W[i]*r[i] for i in range(8)])
1101
if a.reduced_norm() == self.pi:
1102
return a
1103
raise ValueError, "bug"
1104
1105
def all_alpha(self):
1106
return [self.alpha(z) for z in P1ModList(self.P)]
1107
1108
#@cached_function
1109
1110
import os
1111
path = '/tmp/hmf-%s'%os.environ['USER']
1112
if not os.path.exists(path):
1113
os.makedirs(path)
1114
@disk_cached_function(path, memory_cache=True)
1115
def hecke_elements(P):
1116
if P.norm() == 4:
1117
# hardcode this special case.
1118
return [~a for a in hecke_elements_2()]
1119
else:
1120
return [~a for a in AlphaZ(P).all_alpha()]
1121
1122
1123
# Dumb code to get this special case. The answer turns out to be:
1124
# The following elements, divided by 2, where coordinates are in terms of 1,i,j,k, and a=(1+sqrt(5))/2:
1125
# [[-a-1,0,a-2,1], [-a-1,a-1,-a+1,a-1], [-a-1,-a+1,-a+1,a-1], [-a-1,-a+2,-1,0], [-a-1,a-2,-1,0]]
1126
def hecke_elements_2():
1127
P = F.primes_above(2)[0]
1128
from sqrt5_fast import ModN_Reduction
1129
from sage.matrix.all import matrix
1130
1131
f = ModN_Reduction(P)
1132
G = icosian_ring_gens()
1133
k = P.residue_field()
1134
g = k.gen()
1135
def pi(z):
1136
# Note that f(...) returns a string right now, since it's all done at C level.
1137
# This will prboably change, breaking this code.
1138
M = matrix(k,2,eval(f(z).replace(';',','), {'g':g})).transpose()
1139
v = M.echelon_form()[0]
1140
v.set_immutable()
1141
return v
1142
1143
# now just run through elements of icosian ring until we find enough...
1144
ans = {}
1145
a = F.gen()
1146
B = 1
1147
X = [i + a*j for i in range(-B,B+1) for j in range(-B,B+1)]
1148
from sage.misc.all import cartesian_product_iterator
1149
for v in cartesian_product_iterator([X]*4):
1150
z = sum(v[i]*G[i] for i in range(4))
1151
if z.reduced_norm() == 2:
1152
t = pi(z)
1153
if not ans.has_key(t):
1154
ans[t] = z
1155
if len(ans) == 5:
1156
return [x for _, x in ans.iteritems()]
1157
raise RuntimeError
1158
1159
class HMF:
1160
def __init__(self, N):
1161
from sage.all import QuadraticField, QuaternionAlgebra
1162
self._N = N
1163
red, reps, P1 = P1_orbits(N)
1164
self._reduce = red
1165
self._reps = reps
1166
self._P1 = P1
1167
self._theta_N = modp_splitting_map(N)
1168
1169
def __repr__(self):
1170
return "Space of Hilbert modular forms over Q(sqrt(5)) of level %s (norm=%s) and dimension %s"%(
1171
self._N, self._N.norm(), self.dimension())
1172
1173
def dimension(self):
1174
return len(self._reps)
1175
1176
def hecke_matrix(self, P):
1177
mat = []
1178
alpha = AlphaZ(P)
1179
theta = self._theta_N
1180
Z = [theta(x)**(-1) for x in alpha.all_alpha()]
1181
P1 = self._P1
1182
red = self._reduce
1183
for x in self._reps:
1184
row = [0]*len(self._reps)
1185
for w in Z:
1186
y_red = red[P1(w*x)]
1187
row[self._reps.index(y_red)] += 1
1188
mat.append(row)
1189
from sage.all import matrix
1190
return matrix(ZZ, mat)
1191
1192
Tp = hecke_matrix
1193
1194
1195
1196
1197
########################################
1198
# Generalizing to arbitrary level
1199
########################################
1200
1201
def residue_ring(N):
1202
fac = N.factor()
1203
if len(fac) != 1:
1204
raise NotImplementedError, "P must be a prime power"
1205
from sage.rings.all import kronecker_symbol
1206
1207
P, e = fac[0]
1208
p = P.smallest_integer()
1209
s = kronecker_symbol(p, 5)
1210
if e == 1:
1211
return P.residue_field()
1212
if s == 1:
1213
return ResidueRing_split(N, P, p, e)
1214
elif s == -1:
1215
return ResidueRing_inert(N, P, p, e)
1216
else:
1217
if e % 2 == 0:
1218
# easy case
1219
return ResidueRing_ramified_even(N, P, p, e)
1220
else:
1221
# hardest case
1222
return ResidueRing_ramified_odd(N, P, p, e)
1223
1224
class ResidueRing_base:
1225
def __call__(self, x):
1226
if x.parent() is not self._F:
1227
x = self._F(x)
1228
return self._to_ring(x)
1229
1230
def list(self):
1231
return self._ring.list()
1232
1233
def _to_ring(self, x):
1234
return self._ring(x[0]) + self._im_gen*self._ring(x[1])
1235
1236
def ring(self):
1237
return self._ring
1238
1239
def __repr__(self):
1240
return "Residue class ring of ZZ[(1+sqrt(5))/2] modulo %s of characteristic %s"%(
1241
self._N, self._p)
1242
1243
1244
class ResidueRing_split(ResidueRing_base):
1245
"""
1246
Quotient of ring of integers of F by a prime power N.
1247
"""
1248
def __init__(self, N, P, p, e):
1249
self._N = N
1250
self._F = P.number_field()
1251
self._p = p
1252
# Figure out the map to Z/p^e.
1253
fac = self._F.defining_polynomial().factor_padic(p, prec=e+1)
1254
assert len(fac) == 2
1255
roots = [(-a[0][0]).lift() for a in fac]
1256
gen = self._F.gen()
1257
if gen - roots[0] in N:
1258
im_gen = roots[0]
1259
elif gen - roots[1] in N:
1260
im_gen = roots[1]
1261
else:
1262
raise RuntimError, 'bug'
1263
self._ring = ZZ.quotient(p**e)
1264
self._im_gen = self._ring(im_gen)
1265
1266
def lift(self, x):
1267
return self._F(x.lift())
1268
1269
class ResidueRing_inert(ResidueRing_base):
1270
def __init__(self, N, P, p, e):
1271
self._N = N
1272
self._F = P.number_field()
1273
self._p = p
1274
from sage.rings.all import Integers
1275
R = Integers(p**e)
1276
modulus = self._F.defining_polynomial().change_ring(R)
1277
S = R['x']
1278
self._base = R
1279
self._ring = S.quotient_by_principal_ideal(modulus)
1280
self._im_gen = self._ring.gen()
1281
1282
def lift(self, x):
1283
f = x.lift().change_ring(ZZ)
1284
# f is a linear poly in generator of field
1285
return self._F(f)
1286
1287
def list(self):
1288
R = self._ring
1289
x = R.gen()
1290
return [R(a + b*x) for a in self._base for b in self._base]
1291
1292
1293
class ResidueRing_ramified_even(ResidueRing_base):
1294
def __init__(self, N, P, p, e):
1295
self._N = N
1296
self._F = P.number_field()
1297
self._p = p
1298
from sage.rings.all import Integers
1299
assert e%2 == 0
1300
R = Integers(p**(e//2))
1301
modulus = self._F.defining_polynomial().change_ring(R)
1302
S = R['x']
1303
self._ring = S.quotient_by_principal_ideal(modulus)
1304
self._im_gen = self._ring.gen()
1305
1306
def lift(self, x):
1307
f = x.lift().change_ring(ZZ)
1308
return self._F(f)
1309
1310
class ResidueRing_ramified_odd(ResidueRing_base):
1311
"""
1312
Residue class ring R = O_F / P^(2f-1), where e=2f-1
1313
is odd, and P=sqrt(5)O_F is the ramified prime.
1314
1315
Computing with this ring is trickier than all the rest,
1316
since it's not a quotient of Z[x] of the form Z[x]/(m,g),
1317
where m is an integer and g is a polynomial.
1318
The ring R is the quotient of
1319
O_F/P^(2f) = O_F/5^f = (Z/5^fZ)[x]/(x^2-x-1),
1320
by the ideal x^e. We have
1321
O_F/P^(2f-2) subset R subset O_F/P^(2f)
1322
and each successive quotient has order 5 = #(O_F/P).
1323
Thus R has cardinality 5^(2f-1) and characteristic 5^f.
1324
The ring R can't be a quotient of Z[x] of the
1325
form Z[x]/(m,g), since such a quotient has
1326
cardinality m^deg(g) and characteristic m, and
1327
5^(2f-1) is not a power of 5^f.
1328
1329
We thus view R as
1330
1331
R = (Z/5^fZ)[x] / (x^2 - 5, 5^(f-1)*x).
1332
1333
The elements of R are pairs (a,b) in (Z/5^fZ) x (Z/5^(f-1)Z),
1334
which correspond to the class of a + b*x. The arithmetic laws
1335
are thus:
1336
1337
(a,b) + (c,d) = (a+c mod 5^f, b+d mod 5^(f-1))
1338
1339
and
1340
1341
(a,b) * (c,d) = (a*c+b*d*5 mod 5^f, a*d+b*c mod 5^(f-1))
1342
1343
The element omega = F.gen(), which is (1+sqrt(5))/2 maps to
1344
(1+x)/2 = (1/2, 1/2), which is the generator of this ring.
1345
1346
EXAMPLES::
1347
1348
sage: from psage.modform.hilbert.sqrt5.sqrt5 import F, residue_ring
1349
sage: P = F.primes_above(5)[0]; P
1350
Fractional ideal (2*a - 1)
1351
sage: R = residue_ring(P^5)
1352
sage: a = R(F.gen()); a
1353
[0 + 1*sqrt(5)]
1354
sage: a*a
1355
[5 + 0*sqrt(5)]
1356
sage: a*a*a
1357
[0 + 5*sqrt(5)]
1358
sage: a*a*a*a
1359
[0 + 0*sqrt(5)]
1360
"""
1361
def __init__(self, N, P, p, e):
1362
self._N = N
1363
self._F = P.number_field()
1364
self._p = p
1365
from sage.rings.all import Integers
1366
f = e//2 + 1
1367
assert f*2 - 1 == e
1368
R0 = Integers(p**f)
1369
R1 = Integers(p**(f-1))
1370
self._ring = RamifiedProductRing(R0, R1)
1371
self._im_gen = self._ring.gen()
1372
self._sqrt5 = (self._F.gen()*2-1)**2
1373
1374
def lift(self, x):
1375
return x[0].lift() + self._sqrt5 * x[1].lift()
1376
1377
class RamifiedProductRingElement:
1378
def __init__(self, parent, x, check=True):
1379
self._parent = parent
1380
if isinstance(x, RamifiedProductRingElement) and x._parent is parent:
1381
self._x = x
1382
else:
1383
if check:
1384
if isinstance(x, (list, tuple, RamifiedProductRingElement)):
1385
self._x = (parent._R0(x[0]), parent._R1(x[1]))
1386
else:
1387
self._x = (parent._R0(x), parent._R1(0))
1388
else:
1389
self._x = (x[0], x[1])
1390
1391
def __getitem__(self, i):
1392
return self._x[i]
1393
1394
def __repr__(self):
1395
return '[%s + %s*sqrt(5)]'%self._x
1396
1397
def __add__(left, right):
1398
a, b = left._x
1399
c, d = right._x
1400
return RamifiedProductRingElement(left._parent, [a+b, c+d], check=False)
1401
1402
def __sub__(left, right):
1403
a, b = left._x
1404
c, d = right._x
1405
return RamifiedProductRingElement(left._parent, [a-b, c-d], check=False)
1406
1407
def __mul__(left, right):
1408
a, b = left._x
1409
c, d = right._x
1410
return RamifiedProductRingElement(left._parent, [a*c+b*d*5, a*d+b*c], check=False)
1411
1412
class RamifiedProductRing:
1413
def __init__(self, R0, R1):
1414
self._R0 = R0
1415
self._R1 = R1
1416
self._gen = self([ZZ(1)/2, ZZ(1)/2])
1417
1418
def __call__(self, x):
1419
return RamifiedProductRingElement(self, x)
1420
1421
def gen(self):
1422
return self._gen
1423
1424
1425
class Mod_P_reduction_map:
1426
def __init__(self, P):
1427
FAC = P.factor()
1428
assert len(FAC) == 1
1429
self._p, self._e = FAC[0]
1430
self._I, self._J, self._residue_ring = self._compute_IJ(self._p, self._e)
1431
self._IJ = self._I*self._J
1432
1433
def __repr__(self):
1434
return "(Partial) homomorphism from %s onto 2x2 matrices modulo %s^%s"%(
1435
B, self._p._repr_short(), self._e)
1436
1437
def domain(self):
1438
return B
1439
1440
def codomain(self):
1441
return self._I.parent()
1442
1443
def __call__(self, x):
1444
R = self._residue_ring
1445
if x.parent() is not B:
1446
x = B(x)
1447
return R(x[0]) + self._I*R(x[1]) + self._J*R(x[2]) + self._IJ*R(x[3])
1448
1449
def _compute_IJ(self, p, e):
1450
global B, F
1451
if p.number_field() != B.base():
1452
raise ValueError, "p must be a prime ideal in the base field of the quaternion algebra"
1453
if not p.is_prime():
1454
raise ValueError, "p must be prime"
1455
1456
if p.number_field() != B.base():
1457
raise ValueError, "p must be a prime ideal in the base field of the quaternion algebra"
1458
if not p.is_prime():
1459
raise ValueError, "p must be prime"
1460
if F is not p.number_field():
1461
raise ValueError, "p must be a prime of %s"%F
1462
1463
R = residue_ring(p**e)
1464
if isinstance(R, ResidueRing_base):
1465
k = R.ring()
1466
else:
1467
k = R
1468
1469
from sage.all import MatrixSpace
1470
M = MatrixSpace(k, 2)
1471
1472
i2, j2 = B.invariants()
1473
i2 = R(i2); j2 = R(j2)
1474
1475
if k.characteristic() == 2:
1476
raise NotImplementedError
1477
1478
# Find I -- just write it down
1479
I = M([0,i2,1,0])
1480
# Find J -- I figured this out by just writing out the general case
1481
# and seeing what the parameters have to satisfy
1482
i2inv = k(1)/i2
1483
a = None
1484
for b in R.list():
1485
if not b: continue
1486
c = j2 + i2inv * b*b
1487
if c.is_square():
1488
a = -c.sqrt()
1489
break
1490
if a is None:
1491
# do a fallback search; needed in char 3 sometimes.
1492
for J in M:
1493
K = I*J
1494
if J*J == j2 and K == -J*I:
1495
return I, J, K
1496
1497
J = M([a,b,(j2-a*a)/b, -a])
1498
K = I*J
1499
assert K == -J*I, "bug in that I,J don't skew commute"
1500
return I, J, R
1501
1502
1503