Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
241870 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
Fast Cython code needed to compute Hilbert modular forms over F = Q(sqrt(5)).
24
25
All the code in this file is meant to be highly optimized.
26
"""
27
28
include 'stdsage.pxi'
29
include 'cdefs.pxi'
30
include 'python.pxi'
31
32
from sage.rings.ideal import is_Ideal
33
from sage.rings.all import Integers, ZZ, QQ
34
from sage.matrix.all import MatrixSpace, zero_matrix
35
36
from sage.rings.integer cimport Integer
37
from sage.rings
38
39
from sqrt5 import F
40
41
cdef Integer temp1 = Integer(0)
42
43
cdef long SQRT_MAX_LONG = 2**(4*sizeof(long)-1)
44
45
#from sqrt5_fast_python import ResidueRing
46
47
def is_ideal_in_F(I):
48
return is_Ideal(I) and I.number_field() is F
49
50
cpdef long ideal_characteristic(I):
51
return I.number_field()._pari_().idealtwoelt(I._pari_())[0]
52
53
###########################################################################
54
# Logging
55
###########################################################################
56
GLOBAL_VERBOSE=False
57
def global_verbose(s):
58
if GLOBAL_VERBOSE: print s
59
60
###########################################################################
61
# Rings
62
###########################################################################
63
64
def ResidueRing(P, unsigned int e):
65
"""
66
Create the residue class ring O_F/P^e, where P is a prime ideal of the ring
67
O_F of integers of Q(sqrt(5)).
68
69
INPUT:
70
- P -- prime ideal
71
- e -- positive integer
72
73
OUTPUT:
74
- a fast residue class ring
75
76
"""
77
if e <= 0:
78
raise ValueError, "exponent e must be positive"
79
cdef long p = ideal_characteristic(P)
80
if p**e >= SQRT_MAX_LONG:
81
raise ValueError, "residue field must have size less than %s"%SQRT_MAX_LONG
82
if p == 5: # ramified
83
if e % 2 == 0:
84
R = ResidueRing_nonsplit(P, p, e/2)
85
else:
86
R = ResidueRing_ramified_odd(P, p, e)
87
elif p%5 in [2,3]: # nonsplit
88
R = ResidueRing_nonsplit(P, p, e)
89
else: # split
90
R = ResidueRing_split(P, p, e)
91
return R
92
93
class ResidueRingIterator:
94
def __init__(self, R):
95
self.R = R
96
self.i = 0
97
98
def __iter__(self):
99
return self
100
101
def next(self):
102
if self.i < self.R.cardinality():
103
self.i += 1
104
return self.R[self.i-1]
105
else:
106
raise StopIteration
107
108
cdef class ResidueRing_abstract(CommutativeRing):
109
cdef object P, F
110
cdef public object element_class, _residue_field
111
cdef long n0, n1, p, e, _cardinality
112
cdef long im_gen0
113
cdef object element_to_residue_field(self, residue_element x)
114
cdef void add(self, residue_element rop, residue_element op0, residue_element op1)
115
cdef void sub(self, residue_element rop, residue_element op0, residue_element op1)
116
cdef void mul(self, residue_element rop, residue_element op0, residue_element op1)
117
cdef int inv(self, residue_element rop, residue_element op) except -1
118
cdef bint is_unit(self, residue_element op)
119
cdef void neg(self, residue_element rop, residue_element op)
120
cdef ResidueRingElement new_element(self)
121
cdef int coefficients(self, long* v0, long* v1, NumberFieldElement_quadratic x) except -1
122
cdef int coerce_from_nf(self, residue_element r, NumberFieldElement_quadratic x) except -1
123
cdef bint element_is_1(self, residue_element op)
124
cdef bint element_is_0(self, residue_element op)
125
cdef void set_element_to_1(self, residue_element op)
126
cdef void set_element_to_0(self, residue_element op)
127
cdef void set_element(self, residue_element rop, residue_element op)
128
cdef int set_element_from_tuple(self, residue_element rop, x) except -1
129
cdef int cmp_element(self, residue_element left, residue_element right)
130
cdef int pow(self, residue_element rop, residue_element op, long e) except -1
131
cdef bint is_square(self, residue_element op) except -2
132
cdef int sqrt(self, residue_element rop, residue_element op) except -1
133
cdef int ith_element(self, residue_element rop, long i) except -1
134
cpdef long cardinality(self)
135
cdef void unsafe_ith_element(self, residue_element rop, long i)
136
cdef int next_element(self, residue_element rop, residue_element op) except -1
137
cdef bint is_last_element(self, residue_element op)
138
cdef long index_of_element(self, residue_element op) except -1
139
cdef long index_of_element_in_P(self, residue_element op) except -1
140
cdef int next_element_in_P(self, residue_element rop, residue_element op) except -1
141
cdef bint is_last_element_in_P(self, residue_element op)
142
cdef element_to_str(self, residue_element op)
143
def __init__(self, P, p, e):
144
"""
145
INPUT:
146
147
- P -- prime ideal of O_F
148
- e -- positive integer
149
"""
150
self.P = P
151
self.e = e
152
self.p = p
153
self.F = P.number_field()
154
155
# Do not just change the definition of compare.
156
# It is used by the ResidueRing_ModN code to ensure that the prime
157
# over 2 appears first. If you totally changed the ordering, that
158
# would suddenly break.
159
def __richcmp__(ResidueRing_abstract left, right, int op):
160
if left is right:
161
# Make case of easy equality fast
162
return _rich_to_bool(op, 0)
163
if not isinstance(right, ResidueRing_abstract):
164
return _rich_to_bool(op, cmp(ResidueRing_abstract, type(right)))
165
cdef ResidueRing_abstract r = right
166
# slow
167
return _rich_to_bool(op,
168
cmp((left.p,left.e,left.P), (r.p,r.e,r.P)))
169
170
def __hash__(self):
171
return hash((self.p,self.e,self.P))
172
173
def residue_field(self):
174
if self._residue_field is None:
175
self._residue_field = self.P.residue_field()
176
return self._residue_field
177
178
cdef object element_to_residue_field(self, residue_element x):
179
raise NotImplementedError
180
181
def _moduli(self):
182
# useful for testing purposes
183
return self.n0, self.n1
184
185
def __call__(self, x):
186
cdef NumberFieldElement_quadratic y
187
cdef ResidueRingElement z
188
try:
189
y = x
190
z = self.new_element()
191
self.coerce_from_nf(z.x, y)
192
return z
193
except TypeError:
194
pass
195
196
if hasattr(x, 'parent'):
197
P = x.parent()
198
else:
199
P = None
200
if P is self:
201
return x
202
elif P is not self.F:
203
x = self.F(x)
204
return self(x)
205
206
cdef ResidueRingElement new_element(self):
207
raise NotImplementedError
208
209
cdef int coefficients(self, long* v0, long* v1, NumberFieldElement_quadratic x) except -1:
210
# The attributes of x are x.a, x.b and x.denom, defined by
211
# x = (x.a + x.b*sqrt(5))/x.denom.
212
# Let n be the characteristic of self, and assume n is odd. Set
213
# a = x.a (mod n), b = x.b (mod n), e = x.denom^(-1) (mod n).
214
# all long ints.
215
# We have x = (a+sqrt(5)*b)*e (mod n).
216
# The r[0] and r[1] we want are the coefficients of 1 and (1+sqrt(5))/2.
217
# c + d*(1+sqrt(5))/2 = a*e + sqrt(5)*b*e
218
# c + d/2 + (d/2)*sqrt(5) = a*e + sqrt(5)*b*e
219
# So
220
# *v0 = c = a*e - d/2 = a*e - b*e
221
# *v1 = d = 2*b*e
222
#
223
# The above works fine when n is not a power of 2. When n is a power of 2,
224
# it fails, since x.denom is often divisible by 2. Write
225
# x = (x.a + x.b*sqrt(5))/(2*f), so 2*f=x.denom.
226
# If x is 2-integral, then f is not divisible by 2. We have
227
# e = 1/x.denom = 1/(2*f).
228
# Let f' = 1/f (mod n).
229
# We have from the algebra above that in case x.denom % 2 == 0:
230
# *v0 = c =(a-b)*e = (a-b)/(2*f) = ((a-b)/2)/f = (a-b)/2 * f'
231
# *v1 = 2*e*b = b/f = b*f'
232
# Thus an integrality condition is that a=b(mod 2).
233
# If x.denom % 2 != 0, we just proceed as before.
234
235
cdef long a, b, e, t, f, n
236
n = self.n0 if (self.n0 > self.n1) else self.n1
237
cdef int is_even = n%2==0
238
e = mpz_mod_ui(temp1.value, x.denom, n)
239
if e == 0 or (is_even and e%2==0):
240
if is_even:
241
a = mpz_mod_ui(temp1.value, x.a, 2*n)
242
b = mpz_mod_ui(temp1.value, x.b, 2*n)
243
# Special 2-power case, as described in comment above.
244
f = mpz_mod_ui(temp1.value, x.denom, 2*n) / 2
245
if f == 0:
246
raise ZeroDivisionError, "x = %s"%x
247
else:
248
t = a - b
249
if t%2 != 0:
250
raise ZeroDivisionError, "x = %s"%x
251
else:
252
if t < 0:
253
t += 2*n
254
if f != 1:
255
f = invmod_long(f, n)
256
v0[0] = ((t/2) * f)%n
257
v1[0] = (b * f)%n
258
return 0 # success
259
else:
260
raise ZeroDivisionError, "x = %s"%x
261
262
a = mpz_mod_ui(temp1.value, x.a, n) # all are guaranteed >= 0 by GMP docs
263
b = mpz_mod_ui(temp1.value, x.b, n)
264
if e != 1:
265
e = invmod_long(e, n)
266
v0[0] = (a*e)%n - (b*e)%n
267
if v0[0] < 0:
268
v0[0] += n
269
v1[0] = (2*b*e)%n
270
271
272
return 0 # success
273
274
cdef int coerce_from_nf(self, residue_element r, NumberFieldElement_quadratic x) except -1:
275
raise NotImplementedError
276
277
def __repr__(self):
278
return "Residue class ring of %s^%s of characteristic %s"%(
279
self.P._repr_short(), self.e, self.p)
280
281
def lift(self, x): # for consistency with residue fields
282
if x.parent() is self:
283
return x.lift()
284
raise TypeError
285
286
cdef void add(self, residue_element rop, residue_element op0, residue_element op1):
287
raise NotImplementedError
288
cdef void sub(self, residue_element rop, residue_element op0, residue_element op1):
289
raise NotImplementedError
290
cdef void mul(self, residue_element rop, residue_element op0, residue_element op1):
291
raise NotImplementedError
292
cdef int inv(self, residue_element rop, residue_element op) except -1:
293
raise NotImplementedError
294
cdef bint is_unit(self, residue_element op):
295
raise NotImplementedError
296
cdef void neg(self, residue_element rop, residue_element op):
297
raise NotImplementedError
298
299
cdef bint element_is_1(self, residue_element op):
300
return op[0] == 1 and op[1] == 0
301
302
cdef bint element_is_0(self, residue_element op):
303
return op[0] == 0 and op[1] == 0
304
305
cdef void set_element_to_1(self, residue_element op):
306
op[0] = 1
307
op[1] = 0
308
309
cdef void set_element_to_0(self, residue_element op):
310
op[0] = 0
311
op[1] = 0
312
313
cdef void set_element(self, residue_element rop, residue_element op):
314
rop[0] = op[0]
315
rop[1] = op[1]
316
317
cdef int set_element_from_tuple(self, residue_element rop, x) except -1:
318
rop[0] = x[0]%self.n0; rop[1] = x[1]%self.n1
319
return 0
320
321
cdef int cmp_element(self, residue_element left, residue_element right):
322
if left[0] < right[0]:
323
return -1
324
elif left[0] > right[0]:
325
return 1
326
elif left[1] < right[1]:
327
return -1
328
elif left[1] > right[1]:
329
return 1
330
else:
331
return 0
332
333
cdef int pow(self, residue_element rop, residue_element op, long e) except -1:
334
cdef residue_element op2
335
if e < 0:
336
self.inv(op2, op)
337
return self.pow(rop, op2, -e)
338
self.set_element_to_1(rop)
339
cdef residue_element z
340
self.set_element(z, op)
341
while e:
342
if e & 1:
343
self.mul(rop, rop, z)
344
e /= 2
345
if e:
346
self.mul(z, z, z)
347
348
cdef bint is_square(self, residue_element op) except -2:
349
raise NotImplementedError
350
cdef int sqrt(self, residue_element rop, residue_element op) except -1:
351
raise NotImplementedError
352
353
def __getitem__(self, i):
354
cdef ResidueRingElement z = PY_NEW(self.element_class)
355
z._parent = self
356
self.ith_element(z.x, i)
357
return z
358
359
def __iter__(self):
360
return ResidueRingIterator(self)
361
362
def is_finite(self):
363
return True
364
365
cdef int ith_element(self, residue_element rop, long i) except -1:
366
if i < 0 or i >= self.cardinality(): raise IndexError
367
self.unsafe_ith_element(rop, i)
368
369
cpdef long cardinality(self):
370
return self._cardinality
371
372
cdef void unsafe_ith_element(self, residue_element rop, long i):
373
# This assumes 0 <=i < self._cardinality.
374
pass # no point in raising exception...
375
376
cdef int next_element(self, residue_element rop, residue_element op) except -1:
377
# Raises ValueError if there is no next element.
378
# op is assumed valid.
379
raise NotImplementedError
380
381
cdef bint is_last_element(self, residue_element op):
382
raise NotImplementedError
383
384
cdef long index_of_element(self, residue_element op) except -1:
385
# Return the index of the given residue element.
386
raise NotImplementedError
387
388
cdef long index_of_element_in_P(self, residue_element op) except -1:
389
# Return the index of the given residue element, which is
390
# assumed to be in the maximal ideal P. This is the index, as
391
# an element of P.
392
raise NotImplementedError
393
394
cdef int next_element_in_P(self, residue_element rop, residue_element op) except -1:
395
# Sets rop to next element in the enumeration of self that is in P, assuming
396
# that op is in P.
397
# Raises ValueError if there is no next element.
398
# op is assumed valid (i.e., is in P, etc.).
399
raise NotImplementedError
400
401
cdef bint is_last_element_in_P(self, residue_element op):
402
raise NotImplementedError
403
404
cdef element_to_str(self, residue_element op):
405
cdef ResidueRingElement z = PY_NEW(self.element_class)
406
z._parent = self
407
z.x[0] = op[0]
408
z.x[1] = op[1]
409
return str(z)
410
411
412
cdef class ResidueRing_split(ResidueRing_abstract):
413
cdef ResidueRingElement new_element(self):
414
cdef ResidueRingElement_split z = PY_NEW(ResidueRingElement_split)
415
z._parent = self
416
return z
417
418
def __init__(self, P, p, e):
419
ResidueRing_abstract.__init__(self, P, p, e)
420
self.element_class = ResidueRingElement_split
421
self.n0 = p**e
422
self.n1 = 1
423
self._cardinality = self.n0
424
# Compute the mapping by finding roots mod p^e.
425
alpha = P.number_field().gen()
426
cdef long z, z0, z1
427
z = sqrtmod_long(5, p, e)
428
z0 = divmod_long(1+z, 2, p**e) # z = (1+sqrt(5))/2 mod p^e.
429
if alpha - z0 in P:
430
self.im_gen0 = z0
431
else:
432
z1 = divmod_long(1-z, 2, p**e) # z = (1-sqrt(5))/2 mod p^e.
433
if alpha - z1 in P:
434
self.im_gen0 = z1
435
else:
436
raise RuntimeError, "bug -- maybe F is misdefined to have wrong gen"
437
438
cdef int coerce_from_nf(self, residue_element r, NumberFieldElement_quadratic x) except -1:
439
r[1] = 0
440
cdef long v0, v1
441
self.coefficients(&v0, &v1, x)
442
r[0] = v0 + (self.im_gen0*v1)%self.n0
443
if r[0] >= self.n0:
444
r[0] -= self.n0
445
return 0 # success
446
447
def __reduce__(self):
448
return ResidueRing_split, (self.P, self.p, self.e)
449
450
cdef object element_to_residue_field(self, residue_element x):
451
return self.residue_field()(x[0])
452
453
cdef void add(self, residue_element rop, residue_element op0, residue_element op1):
454
rop[0] = (op0[0] + op1[0])%self.n0
455
rop[1] = 0
456
457
cdef void sub(self, residue_element rop, residue_element op0, residue_element op1):
458
rop[0] = (op0[0] - op1[0])%self.n0
459
rop[1] = 0
460
461
cdef void mul(self, residue_element rop, residue_element op0, residue_element op1):
462
rop[0] = (op0[0] * op1[0])%self.n0
463
rop[1] = 0
464
465
cdef int inv(self, residue_element rop, residue_element op) except -1:
466
rop[0] = invmod_long(op[0], self.n0)
467
rop[1] = 0
468
return 0
469
470
cdef bint is_unit(self, residue_element op):
471
return gcd_long(op[0], self.n0) == 1
472
473
cdef void neg(self, residue_element rop, residue_element op):
474
rop[0] = self.n0 - op[0] if op[0] else 0
475
rop[1] = 0
476
477
cdef bint is_square(self, residue_element op) except -2:
478
cdef residue_element rop
479
if op[0] % self.p == 0:
480
# TODO: This is inefficient.
481
try:
482
# do more complicated stuff involving valuation, unit part, etc.
483
self.sqrt(rop, op)
484
except ValueError:
485
return False
486
return True
487
# p is odd and op[0] is a unit, so we just check if op[0] is a
488
# square modulo self.p
489
return kronecker_symbol_long(op[0], self.p) != -1
490
491
cdef int sqrt(self, residue_element rop, residue_element op) except -1:
492
rop[0] = sqrtmod_long(op[0], self.p, self.e)
493
rop[1] = 0
494
if (rop[0] * rop[0])%self.n0 != op[0]:
495
raise ValueError, "element must be a square"
496
return 0
497
498
cdef void unsafe_ith_element(self, residue_element rop, long i):
499
rop[0] = i
500
rop[1] = 0
501
502
cdef int next_element(self, residue_element rop, residue_element op) except -1:
503
rop[0] = op[0] + 1
504
rop[1] = 0
505
if rop[0] >= self.n0:
506
raise ValueError, "no next element"
507
508
cdef bint is_last_element(self, residue_element op):
509
return op[0] == self.n0 - 1
510
511
cdef long index_of_element(self, residue_element op) except -1:
512
# Return the index of the given residue element.
513
return op[0]
514
515
cdef int next_element_in_P(self, residue_element rop, residue_element op) except -1:
516
rop[0] = op[0] + self.p
517
rop[1] = 0
518
if rop[0] >= self.n0:
519
raise ValueError, "no next element in P"
520
return 0
521
522
cdef bint is_last_element_in_P(self, residue_element op):
523
return op[0] == self.n0 - self.p
524
525
cdef long index_of_element_in_P(self, residue_element op) except -1:
526
return op[0]/self.p
527
528
529
530
cdef class ResidueRing_nonsplit(ResidueRing_abstract):
531
cdef ResidueRingElement new_element(self):
532
cdef ResidueRingElement_nonsplit z = PY_NEW(ResidueRingElement_nonsplit)
533
z._parent = self
534
return z
535
536
def __init__(self, P, p, e):
537
ResidueRing_abstract.__init__(self, P, p, e)
538
self.element_class = ResidueRingElement_nonsplit
539
self.n0 = p**e
540
self.n1 = p**e
541
self._cardinality = self.n0 * self.n1
542
543
cdef int coerce_from_nf(self, residue_element r, NumberFieldElement_quadratic x) except -1:
544
# As in the __init__ method from ResidueRingElement_nonsplit, we make r
545
# by letting v = x._coefficients(), then r[0]=v[0] and r[1]=v[1],
546
# reduced mod self.n0 and self.n1.
547
self.coefficients(&r[0], &r[1], x)
548
549
def __reduce__(self):
550
return ResidueRing_nonsplit, (self.P, self.p, self.e)
551
552
cdef object element_to_residue_field(self, residue_element x):
553
k = self.residue_field()
554
if self.p != 5:
555
return k([x[0], x[1]])
556
557
# OK, now p == 5 and power of prime sqrt(5) is even...
558
# We have that self is Z[x]/(5^n, x^2-x-1) --> F_5, where the map sends x to 3, since x^2-x-1=(x+2)^2 (mod 5).
559
# Thus a+b*x |--> a+3*b.
560
return k(x[0] + 3*x[1])
561
562
cdef void add(self, residue_element rop, residue_element op0, residue_element op1):
563
rop[0] = (op0[0] + op1[0])%self.n0
564
rop[1] = (op0[1] + op1[1])%self.n1
565
566
cdef void sub(self, residue_element rop, residue_element op0, residue_element op1):
567
# cython uses python mod normalization convention, so no need to do that manually
568
rop[0] = (op0[0] - op1[0])%self.n0
569
rop[1] = (op0[1] - op1[1])%self.n1
570
571
cdef void mul(self, residue_element rop, residue_element op0, residue_element op1):
572
# it is significantly faster doing these arrays accesses only once in the line below!
573
cdef long a = op0[0], b = op0[1], c = op1[0], d = op1[1]
574
rop[0] = (a*c + b*d)%self.n0
575
rop[1] = (a*d + b*d + b*c)%self.n1
576
577
cdef int inv(self, residue_element rop, residue_element op) except -1:
578
cdef long a = op[0], b = op[1], w
579
w = invmod_long(a*a + a*b - b*b, self.n0)
580
rop[0] = (w*(a+b)) % self.n0
581
rop[1] = (-b*w) % self.n0
582
return 0
583
584
cdef bint is_unit(self, residue_element op):
585
cdef long a = op[0], b = op[1], w
586
return gcd_long(a*a + a*b - b*b, self.n0) == 1
587
588
cdef void neg(self, residue_element rop, residue_element op):
589
rop[0] = self.n0 - op[0] if op[0] else 0
590
rop[1] = self.n1 - op[1] if op[1] else 0
591
592
cdef bint is_square(self, residue_element op) except -2:
593
"""
594
Return True only if op is a perfect square.
595
596
ALGORITHM:
597
We view the residue ring as R[x]/(p^e, x^2-x-1).
598
We first compute the power of p that divides our
599
element a+bx, which is just v=min(ord_p(a),ord_p(b)).
600
If v=infinity, i.e., a+bx=0, then it's a square.
601
If this power is odd, then a+bx can't be a square.
602
Otherwise, it is even, and we next look at the unit
603
part of a+bx = p^v*unit. Then a+bx is a square if and
604
only if the unit part is a square modulo p^(e-v) >= p,
605
since a+bx!=0.
606
When p>2, the unit part is a square if and only if it is a
607
square modulo p, because of Hensel's lemma, and we can check
608
whether or not something is a square modulo p quickly by
609
raising to the power (p^2-1)/2, since
610
"modulo p", means in the ring O_F/p = GF(p^2), since p
611
is an inert prime (this is why we can't use the Legendre
612
symbol).
613
When p=2, it gets complicated to decide if the unit part is a
614
square using code, so just call the sqrt function and catch
615
the exception.
616
"""
617
cdef residue_element unit, z
618
cdef int v = 0
619
cdef long p = self.p
620
621
if op[0] == 0 and op[1] == 0:
622
return True
623
624
if p == 5:
625
try:
626
self.sqrt(z, op)
627
except ValueError:
628
return False
629
return True
630
631
# The "unit" stuff below doesn't make sense for p=5, since
632
# then the maximal ideal is generated by sqrt(5) rather than 5.
633
634
unit[0] = op[0]; unit[1] = op[1]
635
# valuation at p
636
while unit[0]%p ==0 and unit[1]%p==0:
637
unit[0] = unit[0]/p; unit[1] = unit[1]/p
638
v += 1
639
if v%2 != 0:
640
return False # can't be a square
641
642
if p == 2:
643
if self.e == 1:
644
return True # every element is a square, since squaring is an automorphism
645
try:
646
self.sqrt(z, op)
647
except ValueError:
648
return False
649
return True
650
651
# Now unit is not a multiple of p, so it is a unit.
652
self.pow(z, unit, (self.p*self.p-1)/2)
653
654
# The result z is -1 or +1, so z[1]==0 (mod p), no matter what, so no need to look at it.
655
return z[0]%p == 1
656
657
cdef int sqrt(self, residue_element rop, residue_element op) except -1:
658
"""
659
Set rop to a square root of op, if one exists.
660
661
ALGORITHM:
662
We view the residue ring as R[x]/(p^e, x^2-x-1), and want to compute
663
a square root a+bx of c+dx. We have (a+bx)^2=c+dx, so
664
a^2+b^2=c and 2ab+b^2=d.
665
Setting B=b^2 and doing algebra, we get
666
5*B^2 - (4c+2d)B + d^2 = 0,
667
so B = ((4c+2d) +/- sqrt((4c+2d)^2-20d^2))/10.
668
In characteristic 2 or 5, we compute the numerator mod p^(e+1), then
669
divide out by p first, then 10/p.
670
"""
671
if op[0] == 0 and op[1] == 0:
672
# easy special case that is hard to deal with using general algorithm.
673
rop[0] = 0; rop[1] = 0
674
return 0
675
676
# TODO: The following is stupid, and doesn't scale up well... except
677
# that if we're computing with a given level, we will do many
678
# other things that have at least this bad complexity, so this
679
# isn't actually going to slow down anything by more than a
680
# factor of 2. Fix later, when test suite fully in place.
681
# I decided to just do this stupidly, since I spent _hours_ trying
682
# to get a very fast implementation right and failing... Yuck.
683
cdef long a, b
684
cdef residue_element t
685
for a in range(self.n0):
686
rop[0] = a
687
for b in range(self.n1):
688
rop[1] = b
689
self.mul(t, rop, rop)
690
if t[0] == op[0] and t[1] == op[1]:
691
return 0
692
raise ValueError, "not a square"
693
694
## cdef long m, a, b, B, c=op[0], d=op[1], p=self.p, n0=self.n0, n1=self.n1, s, t, e=self.e
695
## if p == 2 or p == 5:
696
## # TODO: This is slow, but painful to implement directly.
697
## # Find p-adic roots B of 5*B^2 - (4c+2d)B + d^2 = 0 to at least precision e+1.
698
## # One of the roots should be the B we seek.
699
## f = ZZ['B']([d*d, -(4*c+2*d), 5])
700
## t = 4*c+2*d
701
## for B in range(n0):
702
## if (5*B*B - t*B + d*d)%n0 == 0:
703
## #for F, exp in f.factor_padic(p, e+1):
704
## #if F.degree() == 1:
705
## #B = (-F[0]).lift()
706
## print "B=", B
707
## try:
708
## rop[1] = sqrtmod_long(B, p, e)
709
## rop[0] = sqrtmod_long((c-B)%n0, p, e)
710
## print "try: %s,%s"%(rop[0],rop[1])
711
## self.mul(z, rop, rop)
712
## if z[0] == op[0] and z[1] == op[1]:
713
## return 0
714
## else: print "fail"
715
## # try other sign for "b":
716
## rop[1] = n0 - rop[1]
717
## print "try: %s,%s"%(rop[0],rop[1])
718
## self.mul(z, rop, rop)
719
## if z[0] == op[0] and z[1] == op[1]:
720
## return 0
721
## else: print "fail"
722
## except ValueError:
723
## pass
724
## raise ValueError
725
726
## # general case (p!=2,5):
727
## t = 4*c + 2*d
728
## s = sqrtmod_long(submod_long(mulmod_long(t,t,n0), 20*mulmod_long(d,d,n0), n0), p, e)
729
## cdef residue_element z
730
## try:
731
## B = divmod_long((t+s)%n0, 10, n0)
732
## rop[1] = sqrtmod_long(B, p, e)
733
## rop[0] = sqrtmod_long((c-B)%n0, p, e)
734
## self.mul(z, rop, rop)
735
## if z[0] == op[0] and z[1] == op[1]:
736
## return 0
737
## # try other sign for "b":
738
## rop[1] = n0 - rop[1]
739
## self.mul(z, rop, rop)
740
## if z[0] == op[0] and z[1] == op[1]:
741
## return 0
742
## except ValueError:
743
## pass
744
## # Try the other choice of sign (other root s).
745
## B = divmod_long((t-s)%n0, 10, n0)
746
## rop[1] = sqrtmod_long(B, p, e)
747
## rop[0] = sqrtmod_long((c-B)%n0, p, e)
748
## self.mul(z, rop, rop)
749
## if z[0] == op[0] and z[1] == op[1]:
750
## return 0
751
## rop[1] = n0 - rop[1]
752
## self.mul(z, rop, rop)
753
## assert z[0] == op[0] and z[1] == op[1]
754
## return 0
755
756
757
758
## cdef int sqrt(self, residue_element rop, residue_element op) except -1:
759
## k = self.residue_field()
760
## if k.degree() == 1:
761
## if self.e == 1:
762
## # happens in ramified case with odd exponent
763
## rop[0] = sqrtmod_long(op[0], self.p, 1)
764
## rop[1] = 0
765
## return 0
766
## raise NotImplementedError, 'sqrt not implemented in non-prime ramified case...'
767
##
768
## # TODO: the stupid overhead in this step alone is vastly more than
769
## # the time to actually compute the sqrt in Givaro or Pari (say)...
770
## a = k([op[0],op[1]]).sqrt()._vector_()
771
##
772
## # The rest of this function is fast (hundreds of times
773
## # faster than above line)...
774
##
775
## rop[0] = a[0]; rop[1] = a[1]
776
## if self.e == 1:
777
## return 0
778
##
779
## if rop[0] == 0 and rop[1] == 0:
780
## # TODO: Finish sqrt when number is 0 mod P in inert case.
781
## raise NotImplementedError
782
##
783
## # Hensel lifting to get square root modulo P^e.
784
## # See the code sqrtmod_long below, which is basically
785
## # the same, but more readable.
786
## cdef int m
787
## cdef long pm, ppm, p
788
## p = self.p; pm = p
789
## # By "x" below, we mean the input op.
790
## # By "y" we mean the square root just computed above, currently rop.
791
## cdef residue_element d, y_squared, y2, t, inv
792
## for m in range(1, self.e):
793
## ppm = p*pm
794
## # We compute ((x-y^2)/p^m) / (2*y)
795
## self.mul(y_squared, rop, rop) # y2 = y^2
796
## self.sub(t, op, y_squared)
797
## assert t[0]%pm == 0 # TODO: remove this after some tests
798
## assert t[1]%pm == 0
799
## t[0] /= pm; t[1] /= pm
800
## # Now t = (x-y^2)/p^m.
801
## y2[0] = (2*rop[0])%ppm; y2[1] = (2*rop[1])%ppm
802
## self.inv(inv, y2)
803
## self.mul(t, t, inv)
804
## # Now t = ((x-y^2)/p^m) / (2*y)
805
## t[0] *= pm; t[1] *= pm
806
## # Now do y = y + pm * t
807
## self.add(rop, rop, t)
808
##
809
## return 0 # success
810
811
cdef void unsafe_ith_element(self, residue_element rop, long i):
812
# Definition: If the element is rop = [a,b], then
813
# we have i = a + b*self.n0
814
rop[0] = i%self.n0
815
rop[1] = (i - rop[0])/self.n0
816
817
cdef int next_element(self, residue_element rop, residue_element op) except -1:
818
rop[0] = op[0] + 1
819
rop[1] = op[1]
820
if rop[0] == self.n0:
821
rop[0] = 0
822
rop[1] += 1
823
if rop[1] >= self.n1:
824
raise ValueError, "no next element"
825
826
cdef bint is_last_element(self, residue_element op):
827
return op[0] == self.n0 - 1 and op[1] == self.n1 - 1
828
829
cdef long index_of_element(self, residue_element op) except -1:
830
# Return the index of the given residue element.
831
return op[0] + op[1]*self.n0
832
833
cdef int next_element_in_P(self, residue_element rop, residue_element op) except -1:
834
"""
835
836
For p = 5, we use the following enumeration, for R=O_F/P^(2e). First, note that we
837
represent R as
838
R = (Z/5^e)[x]/(x^2-x-1)
839
The maximal ideal is the kernel of the natural map to Z/5Z sending x to 3, i.e., it
840
has kernel the principal ideal (x+2).
841
By considering (a+bx)(x+2) = 2a+b + (a+3b)x, we see that the maximal ideal is
842
{ a + (3a+5b)x : a in Z/5^eZ, b in Z/5^(e-1)Z }.
843
We enumerate this by the standard dictionary ordered enumeration
844
on (Z/5^eZ) x (Z/5^(e-1)Z).
845
"""
846
if self.p == 5:
847
# a = op[0]
848
rop[0] = (op[0] + 1)%self.n0
849
rop[1] = (op[1] + 3)%self.n1
850
if rop[0] == 0: # a wraps around
851
# done, i.e., element with a=5^e-1 and b=5^(e-1)-1 last values?
852
# We get the formula below from this calculation:
853
# a+b*x = 5^e-1 + (3*a+5*b)*x. Only the coeff of x matters, which is
854
# 3a+5b = 3*5^e - 3 + 5^e - 5 = 4*5^e - 8.
855
# And of course self.n0 = 5^e, so we use that below.
856
if (rop[1]+5) % self.n1 == 0:
857
raise ValueError, "no next element in P"
858
# not done, so wrap around and increase b by 1
859
rop[0] = 0
860
rop[1] = (rop[1] + 5)%self.n1
861
return 0
862
863
# General case (p!=5):
864
rop[0] = op[0] + self.p
865
rop[1] = op[1]
866
if rop[0] >= self.n0:
867
rop[0] = 0
868
rop[1] += self.p
869
if rop[1] >= self.n1:
870
raise ValueError, "no next element in P"
871
return 0
872
873
cdef bint is_last_element_in_P(self, residue_element op):
874
if self.p == 5:
875
# see the comment above in "next_element_in_P".
876
if self.e == 1:
877
# next element got by incrementing a in enumeration
878
return op[0] == self.n0 - 1
879
else:
880
# next element got by incrementing both a and b by 1 in enumeration,
881
# and 3a+5b=8.
882
return op[0] == self.n0 - 1 and (op[1]+8)%self.n1 == 0
883
# General case (p!=5):
884
return op[0] == self.n0 - self.p and op[1] == self.n1 - self.p
885
886
cdef long index_of_element_in_P(self, residue_element op) except -1:
887
if self.p == 5:
888
# see the comment above in "next_element_in_P".
889
# The index is a + 5^e*b, so we have to recover a and b in op=a+(3a+5b)x.
890
# We have a = op[0], which is easy. Then b=(op[1]-3a)/5
891
return op[0] + ((op[1] - 3*op[0])/5 % (self.n1/self.p)) * self.n0
892
893
# General case (p!=5):
894
return op[0]/self.p + op[1]/self.p * (self.n0/self.p)
895
896
cdef class ResidueRing_ramified_odd(ResidueRing_abstract):
897
cdef ResidueRingElement new_element(self):
898
cdef ResidueRingElement_ramified_odd z = PY_NEW(ResidueRingElement_ramified_odd)
899
z._parent = self
900
return z
901
902
cdef long two_inv
903
def __init__(self, P, p, e):
904
ResidueRing_abstract.__init__(self, P, p, e)
905
# This is assumed in the code for the element_class so it better be true!
906
assert self.F.defining_polynomial().list() == [-1,-1,1]
907
self.n0 = p**(e//2 + 1)
908
self.n1 = p**(e//2)
909
self._cardinality = self.n0 * self.n1
910
self.two_inv = (Integers(self.n0)(2)**(-1)).lift()
911
self.element_class = ResidueRingElement_ramified_odd
912
913
cdef int coerce_from_nf(self, residue_element r, NumberFieldElement_quadratic x) except -1:
914
# see docs for __init__ method of
915
# cdef class ResidueRingElement_ramified_odd(ResidueRingElement)
916
cdef long v0, v1
917
self.coefficients(&v0, &v1, x)
918
if v0 == 0 and v1 == 0:
919
r[0] = 0; r[1] = 0
920
elif v1 == 0:
921
r[0] = v0; r[1] = 0
922
else:
923
r[0] = (v0 + v1*self.two_inv) % self.n0
924
r[1] = (v1*self.two_inv) % self.n1
925
return 0 # success
926
927
def __reduce__(self):
928
return ResidueRing_ramified_odd, (self.P, self.p, self.e)
929
930
cdef object element_to_residue_field(self, residue_element x):
931
k = self.residue_field()
932
# For odd ramified case, we use a different basis, which is:
933
# x[0]+sqrt(5)*x[1], so mapping to residue field mod (sqrt(5))
934
# is easy:
935
return k(x[0])
936
937
cdef void add(self, residue_element rop, residue_element op0, residue_element op1):
938
rop[0] = (op0[0] + op1[0])%self.n0
939
rop[1] = (op0[1] + op1[1])%self.n1
940
941
cdef void sub(self, residue_element rop, residue_element op0, residue_element op1):
942
rop[0] = (op0[0] - op1[0])%self.n0
943
rop[1] = (op0[1] - op1[1])%self.n1
944
945
cdef void mul(self, residue_element rop, residue_element op0, residue_element op1):
946
cdef long a = op0[0], b = op0[1], c = op1[0], d = op1[1]
947
rop[0] = (a*c + 5*b*d)%self.n0
948
rop[1] = (a*d + b*c)%self.n1
949
950
cdef void neg(self, residue_element rop, residue_element op):
951
rop[0] = self.n0 - op[0] if op[0] else 0
952
rop[1] = self.n1 - op[1] if op[1] else 0
953
954
cdef int inv(self, residue_element rop, residue_element op) except -1:
955
"""
956
We derive by algebra the inverse of an element of the quotient ring.
957
We represent the ring as:
958
Z[x]/(x^2-5, x^e) = {a+b*x with 0<=a<p^((e/2)+1), 0<=b<p^(e/2)}
959
Assume a+b*x is a unit, so a!=0(mod 5). Let c+d*x be inverse of a+b*x.
960
We have (a+b*x)*(c+d*x)=1, so ac+5db + (ad+bc)*x = 1. Thus
961
ac+5bd=1 (mod p^((e/2)+1)) and ad+bc=0 (mod p^(e/2)).
962
Doing algebra (multiply both sides by a^(-1) of first, and subtract
963
second equation), we arrive at
964
d = (a^(-1)*b)*(5*a^(-1)*b^2 - a)^(-1) (mod p^(e/2))
965
c = a^(-1)*(1-5*b*d) (mod p^(e/2+1)).
966
Notice that the first equation determines d only modulo p^(e/2), and
967
the second only requires d modulo p^(e/2)!
968
"""
969
cdef long a = op[0], b = op[1], ainv = invmod_long(a, self.n0), n0=self.n0, n1=self.n1
970
# Set rop[1] to "ainv*b*(5*ainv*b*b - a)". We have to use ugly notation to avoid overflow
971
rop[1] = divmod_long(mulmod_long(ainv,b,n1), submod_long(mulmod_long(mulmod_long(mulmod_long(self.p,ainv,n1),b,n1),b,n1), a, n1), n1)
972
# Set rop[0] to "ainv*(1-5*b*d)
973
rop[0] = mulmod_long(ainv, submod_long(1,mulmod_long(5,mulmod_long(b,rop[1],n0),n0),n0),n0)
974
return 0
975
976
cdef bint is_unit(self, residue_element op):
977
"""
978
Return True if the element op=(a,b) is a unit.
979
We represent the ring as
980
Z[x]/(x^2-5, x^e) = {a+b*x with 0<=a<p^((e/2)+1), 0<=b<p^(e/2)}
981
An element is in the maximal ideal (x) if and only if it is of the form:
982
(a+b*x)*x = a*x+b*x*x = a*x + 5*b.
983
Since a is arbitrary, this means the maximal ideal is the set of
984
elements op=(a,b) with a%5==0, so the units are the elements
985
with a%5!=0.
986
"""
987
return op[0]%self.p != 0
988
989
cdef bint is_square(self, residue_element op) except -2:
990
cdef residue_element rop
991
try:
992
self.sqrt(rop, op)
993
except: # ick
994
return False
995
return True
996
997
cdef int sqrt(self, residue_element rop, residue_element op) except -1:
998
"""
999
Algorithm: Given c+dx in Z[x]/(x^2-5,x^e), we seek a+bx with
1000
(a+bx)^2=a^2+5b^2 + 2abx = c+dx,
1001
so a^2+5b^2 = c (mod p^n0)
1002
2ab = d (mod p^n1)
1003
Multiply first equation through by A=a^2, to get quadratic in A:
1004
A^2 - c*A + 5d^2/4 = 0 (mod n0).
1005
Solve for A = (c +/- sqrt(c^2-5d^2))/2.
1006
1007
Thus the algorithm is:
1008
1. Extract sqrt s of c^2-5d^2 modulo n0. If no sqrt, then not square
1009
2. Extract sqrt a of (c+s)/2 or (c-s)/2. Both are squares.
1010
3. Let b=d/(2*a) for each choice of a. Square each and see which works.
1011
"""
1012
# see comment for sqrt above (in inert case).
1013
cdef long a, b
1014
cdef residue_element t
1015
for a in range(self.n0):
1016
rop[0] = a
1017
for b in range(self.n1):
1018
rop[1] = b
1019
self.mul(t, rop, rop)
1020
if t[0] == op[0] and t[1] == op[1]:
1021
return 0
1022
raise ValueError, "not a square"
1023
1024
## cdef long c=op[0], d=op[1], p=self.p, n0=self.n0, n1=self.n1, s, two_inv
1025
## s = sqrtmod_long((c*c - p*d*d)%n0, p, self.e//2 + 1)
1026
## two_inv = invmod_long(2, n0)
1027
## cdef residue_element t
1028
## try:
1029
## rop[0] = sqrtmod_long(((c+s)*two_inv)%n0, p, self.e//2 + 1)
1030
## rop[1] = (divmod_long(d,rop[0],n1) * two_inv)%n1
1031
## self.mul(t, rop, rop)
1032
## if t[0] == op[0] and t[1] == op[1]:
1033
## return 0
1034
## except ValueError:
1035
## pass
1036
## rop[0] = sqrtmod_long(((c-s)*two_inv)%n0, p, self.e//2 + 1)
1037
## rop[1] = (divmod_long(d,rop[0],n1) * two_inv)%n1
1038
## self.mul(t, rop, rop)
1039
## assert t[0] == op[0] and t[1] == op[1]
1040
## return 0
1041
1042
1043
cdef void unsafe_ith_element(self, residue_element rop, long i):
1044
# Definition: If the element is rop = [a,b], then
1045
# we have i = a + b*self.n0
1046
rop[0] = i%self.n0
1047
rop[1] = (i - rop[0])/self.n0
1048
1049
cdef int next_element(self, residue_element rop, residue_element op) except -1:
1050
rop[0] = op[0] + 1
1051
rop[1] = op[1]
1052
if rop[0] == self.n0:
1053
rop[0] = 0
1054
rop[1] += 1
1055
if rop[1] >= self.n1:
1056
raise ValueError, "no next element"
1057
1058
cdef bint is_last_element(self, residue_element op):
1059
return op[0] == self.n0 - 1 and op[1] == self.n1 - 1
1060
1061
cdef long index_of_element(self, residue_element op) except -1:
1062
# Return the index of the given residue element.
1063
return op[0] + op[1]*self.n0
1064
1065
cdef int next_element_in_P(self, residue_element rop, residue_element op) except -1:
1066
rop[0] = op[0] + self.p
1067
if rop[0] == self.n0:
1068
rop[0] = 0
1069
rop[1] += 1
1070
if rop[1] >= self.n1:
1071
raise ValueError, "no next element"
1072
1073
cdef bint is_last_element_in_P(self, residue_element op):
1074
return op[0] == self.n0 - self.p and op[1] == self.n1 - 1
1075
1076
cdef long index_of_element_in_P(self, residue_element op) except -1:
1077
return op[0]/self.p + op[1] * (self.n0/self.p)
1078
1079
1080
###########################################################################
1081
# Ring elements
1082
###########################################################################
1083
cdef inline bint _rich_to_bool(int op, int r): # copied from sage.structure.element...
1084
if op == Py_LT: #<
1085
return (r < 0)
1086
elif op == Py_EQ: #==
1087
return (r == 0)
1088
elif op == Py_GT: #>
1089
return (r > 0)
1090
elif op == Py_LE: #<=
1091
return (r <= 0)
1092
elif op == Py_NE: #!=
1093
return (r != 0)
1094
elif op == Py_GE: #>=
1095
return (r >= 0)
1096
1097
1098
cdef class ResidueRingElement:
1099
cpdef parent(self):
1100
return self._parent
1101
cdef new_element(self):
1102
raise NotImplementedError
1103
1104
cpdef long index(self):
1105
"""
1106
Return the index of this element in the enumeration of
1107
elements of the parent.
1108
"""
1109
return self._parent.index_of_element(self.x)
1110
1111
def __add__(ResidueRingElement left, ResidueRingElement right):
1112
cdef ResidueRingElement z = left.new_element()
1113
left._parent.add(z.x, left.x, right.x)
1114
return z
1115
def __sub__(ResidueRingElement left, ResidueRingElement right):
1116
cdef ResidueRingElement z = left.new_element()
1117
left._parent.sub(z.x, left.x, right.x)
1118
return z
1119
def __mul__(ResidueRingElement left, ResidueRingElement right):
1120
cdef ResidueRingElement z = left.new_element()
1121
left._parent.mul(z.x, left.x, right.x)
1122
return z
1123
def __div__(ResidueRingElement left, ResidueRingElement right):
1124
cdef ResidueRingElement z = left.new_element()
1125
left._parent.inv(z.x, right.x)
1126
left._parent.mul(z.x, left.x, z.x)
1127
return z
1128
def __neg__(ResidueRingElement self):
1129
cdef ResidueRingElement z = self.new_element()
1130
self._parent.neg(z.x, self.x)
1131
return z
1132
def __pow__(ResidueRingElement self, e, m):
1133
cdef ResidueRingElement z = self.new_element()
1134
self._parent.pow(z.x, self.x, e)
1135
return z
1136
1137
def __invert__(ResidueRingElement self):
1138
cdef ResidueRingElement z = self.new_element()
1139
self._parent.inv(z.x, self.x)
1140
return z
1141
1142
def __richcmp__(ResidueRingElement left, ResidueRingElement right, int op):
1143
cdef int c
1144
if left.x[0] < right.x[0]:
1145
c = -1
1146
elif left.x[0] > right.x[0]:
1147
c = 1
1148
elif left.x[1] < right.x[1]:
1149
c = -1
1150
elif left.x[1] > right.x[1]:
1151
c = 1
1152
else:
1153
c = 0
1154
return _rich_to_bool(op, c)
1155
1156
def __hash__(self):
1157
return self.x[0] + self._parent.n0*self.x[1]
1158
1159
cpdef bint is_unit(self):
1160
return self._parent.is_unit(self.x)
1161
1162
cpdef bint is_square(self):
1163
return self._parent.is_square(self.x)
1164
1165
cpdef sqrt(self):
1166
cdef ResidueRingElement z = self.new_element()
1167
self._parent.sqrt(z.x, self.x)
1168
return z
1169
1170
cdef class ResidueRingElement_split(ResidueRingElement):
1171
def __init__(self, ResidueRing_split parent, x):
1172
self._parent = parent
1173
assert x.parent() is parent.F
1174
v = x._coefficients()
1175
self.x[1] = 0
1176
if len(v) == 0:
1177
self.x[0] = 0
1178
return
1179
elif len(v) == 1:
1180
self.x[0] = v[0] % self._parent.n0
1181
return
1182
self.x[0] = v[0] + parent.im_gen0*v[1]
1183
self.x[0] = self.x[0] % self._parent.n0
1184
self.x[1] = 0
1185
1186
cdef new_element(self):
1187
cdef ResidueRingElement_split z = PY_NEW(ResidueRingElement_split)
1188
z._parent = self._parent
1189
return z
1190
1191
def __repr__(self):
1192
return str(self.x[0])
1193
1194
def lift(self):
1195
"""
1196
Return lift of element to number field F.
1197
"""
1198
return self._parent.F(self.x[0])
1199
1200
cdef class ResidueRingElement_nonsplit(ResidueRingElement):
1201
"""
1202
EXAMPLES::
1203
1204
sage: from psage.modform.hilbert.sqrt5.sqrt5_fast import ResidueRing
1205
1206
Inert example::
1207
1208
sage: F.<a> = NumberField(x^2 - x - 1)
1209
sage: P_inert = F.primes_above(3)[0]
1210
sage: R_inert = ResidueRing(P_inert, 2)
1211
sage: s = R_inert(F.0); s
1212
g
1213
sage: s + s + s
1214
3*g
1215
sage: s*s*s*s
1216
2 + 3*g
1217
sage: R_inert(F.0^4)
1218
2 + 3*g
1219
1220
Ramified example (with even exponent)::
1221
1222
sage: P = F.primes_above(5)[0]
1223
sage: R = ResidueRing(P, 2)
1224
sage: s = R(F.0); s
1225
g
1226
sage: s*s*s*s*s
1227
3
1228
sage: R(F.0^5)
1229
3
1230
sage: a = (s+s - R(F(1))); a*a
1231
0
1232
sage: R = ResidueRing(P, 3)
1233
sage: t = R(2*F.0-1); t # reduction of sqrt(5)
1234
s
1235
sage: t*t*t
1236
0
1237
"""
1238
def __init__(self, ResidueRing_nonsplit parent, x):
1239
self._parent = parent
1240
assert x.parent() is parent.F
1241
v = x._coefficients()
1242
if len(v) == 0:
1243
self.x[0] = 0; self.x[1] = 0
1244
return
1245
elif len(v) == 1:
1246
self.x[0] = v[0]
1247
self.x[1] = 0
1248
else:
1249
self.x[0] = v[0]
1250
self.x[1] = v[1]
1251
self.x[0] = self.x[0] % self._parent.n0
1252
self.x[1] = self.x[1] % self._parent.n1
1253
1254
cdef new_element(self):
1255
cdef ResidueRingElement_nonsplit z = PY_NEW(ResidueRingElement_nonsplit)
1256
z._parent = self._parent
1257
return z
1258
1259
def __repr__(self):
1260
if self.x[0]:
1261
if self.x[1]:
1262
if self.x[1] == 1:
1263
return '%s + g'%self.x[0]
1264
else:
1265
return '%s + %s*g'%(self.x[0], self.x[1])
1266
return str(self.x[0])
1267
else:
1268
if self.x[1]:
1269
if self.x[1] == 1:
1270
return 'g'
1271
else:
1272
return '%s*g'%self.x[1]
1273
return '0'
1274
1275
def lift(self):
1276
"""
1277
Return lift of element to number field F.
1278
"""
1279
# TODO: test...
1280
return self._parent.F([self.x[0], self.x[1]])
1281
1282
cdef class ResidueRingElement_ramified_odd(ResidueRingElement):
1283
"""
1284
Element of residue class ring R = O_F / P^(2f-1), where e=2f-1 is
1285
odd, and P=sqrt(5)O_F is the ramified prime.
1286
1287
Computing with this ring is trickier than all the rest,
1288
since it's not a quotient of Z[x] of the form Z[x]/(m,g),
1289
where m is an integer and g is a polynomial.
1290
The ring R is the quotient of
1291
O_F/P^(2f) = O_F/5^f = (Z/5^fZ)[x]/(x^2-x-1),
1292
by the ideal x^e. We have
1293
O_F/P^(2f-2) subset R subset O_F/P^(2f)
1294
and each successive quotient has order 5 = #(O_F/P).
1295
Thus R has cardinality 5^(2f-1) and characteristic 5^f.
1296
The ring R can't be a quotient of Z[x] of the
1297
form Z[x]/(m,g), since such a quotient has
1298
cardinality m^deg(g) and characteristic m, and
1299
5^(2f-1) is not a power of 5^f.
1300
1301
We thus view R as
1302
1303
R = (Z/5^fZ)[x] / (x^2 - 5, 5^(f-1)*x).
1304
1305
The elements of R are pairs (a,b) in (Z/5^fZ) x (Z/5^(f-1)Z),
1306
which correspond to the class of a + b*x. The arithmetic laws
1307
are thus:
1308
1309
(a,b) + (c,d) = (a+c mod 5^f, b+d mod 5^(f-1))
1310
1311
and
1312
1313
(a,b) * (c,d) = (a*c+b*d*5 mod 5^f, a*d+b*c mod 5^(f-1))
1314
1315
The element omega = F.gen(), which is (1+sqrt(5))/2 maps to
1316
(1+x)/2 = (1/2, 1/2), which is the generator of this ring.
1317
"""
1318
def __init__(self, ResidueRing_ramified_odd parent, x):
1319
self._parent = parent
1320
assert x.parent() is parent.F
1321
# We can assume that the defining poly of F is T^2-T-1 (this is asserted
1322
# in some code above), so the gen is (1+sqrt(5))/2. We can also
1323
# assume x has no denom. Then sqrt(5)=2*x-1, so need to find c,d such that:
1324
# a + b*x = c + d*(2*x-1)
1325
# ===> c = a + b/2, d = b/2
1326
cdef long a, b
1327
v = x._coefficients()
1328
if len(v) == 0:
1329
self.x[0] = 0; self.x[1] = 0
1330
return
1331
if len(v) == 1:
1332
self.x[0] = v[0]
1333
self.x[1] = 0
1334
else:
1335
a = v[0]; b = v[1]
1336
self.x[0] = a + b*parent.two_inv
1337
self.x[1] = b*parent.two_inv
1338
self.x[0] = self.x[0] % self._parent.n0
1339
self.x[1] = self.x[1] % self._parent.n1
1340
1341
cdef new_element(self):
1342
cdef ResidueRingElement_ramified_odd z = PY_NEW(ResidueRingElement_ramified_odd)
1343
z._parent = self._parent
1344
return z
1345
1346
def __repr__(self):
1347
if self.x[0]:
1348
if self.x[1]:
1349
if self.x[1] == 1:
1350
return '%s + s'%self.x[0]
1351
else:
1352
return '%s + %s*s'%(self.x[0], self.x[1])
1353
return str(self.x[0])
1354
else:
1355
if self.x[1]:
1356
if self.x[1] == 1:
1357
return 's'
1358
else:
1359
return '%s*s'%self.x[1]
1360
return '0'
1361
1362
1363
####################################################################
1364
# misc arithmetic needed elsewhere
1365
####################################################################
1366
1367
cdef mpz_t mpz_temp
1368
mpz_init(mpz_temp)
1369
cdef long kronecker_symbol_long(long a, long b):
1370
mpz_set_si(mpz_temp, b)
1371
return mpz_si_kronecker(a, mpz_temp)
1372
1373
cdef inline long mulmod_long(long x, long y, long m):
1374
return (x*y)%m
1375
1376
cdef inline long submod_long(long x, long y, long m):
1377
return (x-y)%m
1378
1379
cdef inline long addmod_long(long x, long y, long m):
1380
return (x+y)%m
1381
1382
#cdef long kronecker_symbol_long2(long a, long p):
1383
# return powmod_long(a, (p-1)/2, p)
1384
1385
cdef long powmod_long(long x, long e, long m):
1386
# return x^m mod e, where x and e assumed < SQRT_MAX_LONG
1387
cdef long y = 1, z = x
1388
while e:
1389
if e & 1:
1390
y = (y * z) % m
1391
e /= 2
1392
if e: z = (z * z) % m
1393
return y
1394
1395
# This is kind of ugly...
1396
from sage.rings.fast_arith cimport arith_llong
1397
cdef arith_llong Arith_llong = arith_llong()
1398
cdef long invmod_long(long x, long m) except -1:
1399
cdef long long g, s, t
1400
g = Arith_llong.c_xgcd_longlong(x, m, &s, &t)
1401
if g != 1:
1402
raise ZeroDivisionError
1403
return s%m
1404
1405
cdef long gcd_long(long a, long b) except -1:
1406
return Arith_llong.c_gcd_longlong(a, b)
1407
1408
cdef long divmod_long(long x, long y, long m) except -1:
1409
#return quotient x/y mod m, assuming x, y < SQRT_MAX_LONG
1410
return (x * invmod_long(y, m)) % m
1411
1412
cpdef long sqrtmod_long(long x, long p, int n) except -1:
1413
cdef long a, r, y, z
1414
if n <= 0:
1415
raise ValueError, "n must be positive"
1416
1417
if x == 0 or x == 1:
1418
# easy special case that works no matter what p is.
1419
return x
1420
1421
if p == 2: # pain in the butt case
1422
if n == 1:
1423
return x
1424
elif n == 2:
1425
# since x isn't 1 or 2 (see above special case)
1426
raise ValueError, "not a square"
1427
elif n == 3:
1428
if x == 4: return 2
1429
# since x isn't 1 or 2 or 4 (see above special case)
1430
raise ValueError, "not a square"
1431
elif n == 4:
1432
if x == 4: return 2
1433
if x == 9: return 3
1434
raise ValueError, "not a square"
1435
else:
1436
# a general slow algorithm -- use pari; this won't get called much
1437
try:
1438
# making a string as below is still *much* faster than
1439
# just doing say: "Zp(2,20)(16).sqrt()". Why? Because
1440
# even evaluating 'Zp(2,20)' in sage takes longer than
1441
# creating and evaluating the whole string with pari!
1442
# And then the way Zp in Sage computes the square root is via...
1443
# making the corresponding Pari object.
1444
from sage.libs.pari.all import pari, PariError
1445
cmd = "lift(sqrt(%s+O(2^%s)))%%(2^%s)"%(x, 2*n, n)
1446
return int(pari(cmd))
1447
except PariError:
1448
raise ValueError, "not a square"
1449
1450
if x%p == 0:
1451
a = x/p
1452
y = 1
1453
r = 1
1454
while r < n and a%p == 0:
1455
a /= p
1456
r += 1
1457
if r%2 == 0:
1458
y *= p
1459
if r % 2 != 0:
1460
raise ValueError, "not a square"
1461
return y * sqrtmod_long(a, p, n - r//2)
1462
1463
# Compute square root of x modulo p^n using Hensel lifting, where
1464
# p id odd.
1465
1466
# Step 1. Find a square root modulo p. (See section 4.5 of my Elementary Number Theory book.)
1467
if p % 4 == 3:
1468
# Easy case when p is 3 mod 4.
1469
y = powmod_long(x, (p+1)/4, p)
1470
elif p == 5: # hardcode useful special case
1471
z = x % p
1472
if z == 0 or z == 1:
1473
y = x
1474
elif z == 2 or z == 3:
1475
raise ValueError
1476
elif z == 4:
1477
y = 2
1478
else:
1479
assert False, 'bug'
1480
else:
1481
# Harder case -- no known poly time deterministic algorithm.
1482
# TODO: There is a fast algorithm we could implement here, but
1483
# I'll just use pari for now and come back to this later. This
1484
# is on page 88 of my book, esp. since this is not critical
1485
# to the speed of the whole algorithm.
1486
# Another reason to redo -- stupid PariError is hard to trap...
1487
from sage.all import pari
1488
y = pari(x).Mod(p).sqrt().lift()
1489
1490
if n == 1: # done
1491
return y
1492
1493
# Step 2. Hensel lift to mod p^n.
1494
cdef int m
1495
cdef long t, pm, ppm
1496
pm = p
1497
for m in range(1, n):
1498
ppm = p*pm
1499
# Compute t = ((x-y^2)/p^m) / (2*y)
1500
t = divmod_long( ((x - y*y)%ppm) / pm, (2*y)%ppm, ppm)
1501
# And set y = y + pm*t, which gives next correct approximation for y.
1502
y += (pm * t) % ppm
1503
pm = ppm
1504
1505
return y
1506
1507
###########################################################################
1508
# The quotient ring O_F/N
1509
###########################################################################
1510
1511
# The worse case is the inert prime 2 (of norm 4). The maximum level possible is 2^32.
1512
cdef enum:
1513
MAX_PRIME_DIVISORS = 16
1514
1515
ctypedef residue_element modn_element[MAX_PRIME_DIVISORS]
1516
1517
# 2 x 2 matrices over O_F/N
1518
ctypedef modn_element modn_matrix[4]
1519
1520
1521
cdef class ResidueRingModN:
1522
cdef public list residue_rings
1523
cdef object N
1524
cdef int r # number of prime divisors
1525
1526
def __init__(self, N):
1527
self.N = N
1528
1529
# A guarantee we make for other code is that if 2 divides N,
1530
# then the first factor below will be 2. Thus we sort the
1531
# factors by their residue characteristic in
1532
# order to ensure this. It is also ** SUPER IMPORTANT ** that
1533
# the order *not* depend on the exponent, so we next sort by gens_reduced, and finally by exponent.
1534
v = [(P.smallest_integer(), P.gens_reduced()[0], e, ResidueRing(P, e)) for P, e in N.factor()]
1535
v.sort()
1536
self.residue_rings = [x[-1] for x in v]
1537
self.r = len(self.residue_rings)
1538
1539
def __repr__(self):
1540
return "Residue class ring modulo the ideal %s of norm %s"%(
1541
self.N._repr_short(), self.N.norm())
1542
1543
cdef int set(self, modn_element rop, x) except -1:
1544
self.coerce_from_nf(rop, self.N.number_field()(x), 0)
1545
1546
cdef int coerce_from_nf(self, modn_element rop, NumberFieldElement_quadratic op, int odd_only) except -1:
1547
# Given an element op in the field F, try to reduce it modulo
1548
# N and create the corresponding modn element.
1549
# If the odd_only flag is set to 1, leave the result locally
1550
# at the primes over 2 undefined.
1551
cdef int i
1552
cdef ResidueRing_abstract R
1553
for i in range(self.r):
1554
R = self.residue_rings[i]
1555
if odd_only and R.p == 2:
1556
continue
1557
R.coerce_from_nf(rop[i], op)
1558
return 0 # success
1559
1560
cdef void set_element(self, modn_element rop, modn_element op):
1561
cdef int i
1562
for i in range(self.r):
1563
rop[i][0] = op[i][0]
1564
rop[i][1] = op[i][1]
1565
1566
cdef void set_element_omitting_ith_factor(self, modn_element rop, modn_element op, int i):
1567
cdef int j
1568
for j in range(i):
1569
rop[j][0] = op[j][0]
1570
rop[j][1] = op[j][1]
1571
for j in range(i+1, self.r):
1572
rop[j-1][0] = op[j][0]
1573
rop[j-1][1] = op[j][1]
1574
1575
cdef int set_element_reducing_exponent_of_ith_factor(self, modn_element rop, modn_element op, int i) except -1:
1576
cdef ResidueRing_abstract R
1577
cdef int j, e
1578
cdef long p, temp
1579
R = self.residue_rings[i]
1580
e = R.e
1581
p = R.p
1582
# No question what to do with everything before i-th factor:
1583
for j in range(i):
1584
rop[j][0] = op[j][0]
1585
rop[j][1] = op[j][1]
1586
1587
if (e == 1 and p!=5) or (p == 5 and e == 1 and isinstance(R, ResidueRing_ramified_odd)):
1588
# Easy: just delete the i-th factor completely.
1589
# Now fill in the rest
1590
for j in range(i+1, self.r):
1591
rop[j-1][0] = op[j][0]
1592
rop[j-1][1] = op[j][1]
1593
elif p != 5:
1594
# If p != 5 then the prime is unramified, so the representative
1595
# a+b*x for op just works so long as we reduce it mod R.n0/p and R.n1/p.
1596
rop[i][0] = op[i][0] % (R.n0//p)
1597
if R.n1 > 1: # otherwise this factor doesn't matter (and we're in the split case)
1598
rop[i][1] = op[i][1] % (R.n1//p)
1599
else:
1600
rop[i][1] = 0
1601
1602
# And fill in the rest
1603
for j in range(i+1, self.r):
1604
rop[j][0] = op[j][0]
1605
rop[j][1] = op[j][1]
1606
else:
1607
assert p == 5
1608
# Now we're in the ramified case, which is the hardest, since we use
1609
# completely different representations for O_F / (sqrt(5))^e, for e even
1610
# and for e odd. Also, note that the e above (i.e., R.e) in this case
1611
# isn't even the power of P=(sqrt(5)) in the even case.
1612
# Recall that we represent O_F / (sqrt(5))^e for e even as
1613
# (Z/5^(e/2)Z)[x]/(x^2-x-1)
1614
# and for e odd as
1615
# {a + b*sqrt(5) : a < p^(e//2 + 1), b < p^(e//2)}
1616
1617
if isinstance(R, ResidueRing_ramified_odd):
1618
# Case 1: odd power >= 3 of sqrt(5) :
1619
# We have a+b*sqrt(5), and we need to write it as
1620
# c+d*x, where x=(1+sqrt(5))/2, so 2*x-1 = sqrt(5).
1621
# Thus a+b*sqrt(5) = a+b*(2*x-1) = a-b + 2*b*x
1622
#print "odd ramified case"
1623
#print op[i][0],op[i][1],
1624
rop[i][0] = op[i][0]-op[i][1]
1625
rop[i][1] = 2*op[i][1]
1626
#print " |--->", rop[i][0], rop[i][1]
1627
else:
1628
#print "even ramified case"
1629
# Case 2: even power >= 2 of sqrt(5)
1630
# We have a+b*x and have to write in terms of sqrt(5), so
1631
# a+b*x = a+b*(1+sqrt(5))/2 = a+b/2 + (b/2)*sqrt(5)
1632
temp = divmod_long(op[i][1], 2, R.n1)
1633
rop[i][0] = (op[i][0] + temp)%R.n1
1634
rop[i][1] = temp
1635
# Fill in the rest.
1636
for j in range(i+1, self.r):
1637
rop[j][0] = op[j][0]
1638
rop[j][1] = op[j][1]
1639
return 0
1640
1641
cdef element_to_str(self, modn_element op):
1642
s = ','.join([(<ResidueRing_abstract>self.residue_rings[i]).element_to_str(op[i]) for
1643
i in range(self.r)])
1644
if self.r > 1:
1645
s = '(' + s + ')'
1646
return s
1647
1648
cdef int cmp_element(self, modn_element op0, modn_element op1):
1649
cdef int i, c
1650
cdef ResidueRing_abstract R
1651
for i in range(self.r):
1652
R = self.residue_rings[i]
1653
c = R.cmp_element(op0[i], op1[i])
1654
if c: return c
1655
return 0
1656
1657
cdef void mul(self, modn_element rop, modn_element op0, modn_element op1):
1658
cdef int i
1659
cdef ResidueRing_abstract R
1660
for i in range(self.r):
1661
R = self.residue_rings[i]
1662
R.mul(rop[i], op0[i], op1[i])
1663
1664
cdef int inv(self, modn_element rop, modn_element op) except -1:
1665
cdef int i
1666
cdef ResidueRing_abstract R
1667
for i in range(self.r):
1668
R = self.residue_rings[i]
1669
R.inv(rop[i], op[i])
1670
return 0
1671
1672
cdef int neg(self, modn_element rop, modn_element op) except -1:
1673
cdef int i
1674
cdef ResidueRing_abstract R
1675
for i in range(self.r):
1676
R = self.residue_rings[i]
1677
R.neg(rop[i], op[i])
1678
return 0
1679
1680
cdef void add(self, modn_element rop, modn_element op0, modn_element op1):
1681
cdef int i
1682
cdef ResidueRing_abstract R
1683
for i in range(self.r):
1684
R = self.residue_rings[i]
1685
R.add(rop[i], op0[i], op1[i])
1686
1687
cdef void sub(self, modn_element rop, modn_element op0, modn_element op1):
1688
cdef int i
1689
cdef ResidueRing_abstract R
1690
for i in range(self.r):
1691
R = self.residue_rings[i]
1692
R.sub(rop[i], op0[i], op1[i])
1693
1694
cdef bint is_last_element(self, modn_element x):
1695
cdef int i
1696
cdef ResidueRing_abstract R
1697
for i in range(self.r):
1698
R = self.residue_rings[i]
1699
if not R.is_last_element(x[i]):
1700
return False
1701
return True
1702
1703
cdef int next_element(self, modn_element rop, modn_element op) except -1:
1704
cdef int i, done = 0
1705
cdef ResidueRing_abstract R
1706
for i in range(self.r):
1707
R = self.residue_rings[i]
1708
if done:
1709
R.set_element(rop[i], op[i])
1710
else:
1711
if R.is_last_element(op[i]):
1712
R.set_element_to_0(rop[i])
1713
else:
1714
R.next_element(rop[i], op[i])
1715
done = True
1716
1717
cdef bint is_square(self, modn_element op):
1718
cdef int i
1719
cdef ResidueRing_abstract R
1720
for i in range(self.r):
1721
R = self.residue_rings[i]
1722
if not R.is_square(op[i]):
1723
return False
1724
return True
1725
1726
cdef int sqrt(self, modn_element rop, modn_element op) except -1:
1727
cdef int i
1728
cdef ResidueRing_abstract R
1729
for i in range(self.r):
1730
R = self.residue_rings[i]
1731
R.sqrt(rop[i], op[i])
1732
return 0
1733
1734
#######################################
1735
# modn_matrices
1736
#######################################
1737
cdef matrix_to_str(self, modn_matrix A):
1738
return '[%s, %s; %s, %s]'%tuple([self.element_to_str(A[i]) for i in range(4)])
1739
1740
cdef bint matrix_is_invertible(self, modn_matrix x):
1741
# det = x[0]*x[3] - x[1]*x[2], so we return True
1742
# when x[0]*x[3] != x[1]*x[2]
1743
cdef modn_element t1, t2
1744
self.mul(t1, x[0], x[3])
1745
self.mul(t2, x[1], x[2])
1746
return self.cmp_element(t1, t2) != 0
1747
1748
cdef int matrix_inv(self, modn_matrix rop, modn_matrix x) except -1:
1749
cdef modn_element d, t1, t2
1750
self.mul(t1, x[0], x[3])
1751
self.mul(t2, x[1], x[2])
1752
self.sub(d, t1, t2) # x[0]*x[2] - x[1]*x[3]
1753
self.inv(d, d)
1754
# Inverse is [x3,-x1,-x2,x0] * d
1755
self.set_element(rop[0], x[3])
1756
self.set_element(rop[3], x[0])
1757
self.neg(t1, x[1])
1758
self.neg(t2, x[2])
1759
self.set_element(rop[1], t1)
1760
self.set_element(rop[2], t2)
1761
cdef int i
1762
for i in range(4):
1763
self.mul(rop[i], rop[i], d)
1764
return 0 # success
1765
1766
cdef matrix_mul(self, modn_matrix rop, modn_matrix x, modn_matrix y):
1767
cdef modn_element t, t2
1768
self.mul(t, x[0], y[0])
1769
self.mul(t2, x[1], y[2])
1770
self.add(rop[0], t, t2)
1771
self.mul(t, x[0], y[1])
1772
self.mul(t2, x[1], y[3])
1773
self.add(rop[1], t, t2)
1774
self.mul(t, x[2], y[0])
1775
self.mul(t2, x[3], y[2])
1776
self.add(rop[2], t, t2)
1777
self.mul(t, x[2], y[1])
1778
self.mul(t2, x[3], y[3])
1779
self.add(rop[3], t, t2)
1780
1781
cdef matrix_mul_ith_factor(self, modn_matrix rop, modn_matrix x, modn_matrix y, int i):
1782
cdef ResidueRing_abstract R = self.residue_rings[i]
1783
cdef residue_element t, t2
1784
R.mul(t, x[0][i], y[0][i])
1785
R.mul(t2, x[1][i], y[2][i])
1786
R.add(rop[0][i], t, t2)
1787
R.mul(t, x[0][i], y[1][i])
1788
R.mul(t2, x[1][i], y[3][i])
1789
R.add(rop[1][i], t, t2)
1790
R.mul(t, x[2][i], y[0][i])
1791
R.mul(t2, x[3][i], y[2][i])
1792
R.add(rop[2][i], t, t2)
1793
R.mul(t, x[2][i], y[1][i])
1794
R.mul(t2, x[3][i], y[3][i])
1795
R.add(rop[3][i], t, t2)
1796
1797
###########################################################################
1798
# The projective line P^1(O_F/N)
1799
###########################################################################
1800
ctypedef modn_element p1_element[2]
1801
1802
cdef class ProjectiveLineModN:
1803
cdef ResidueRingModN S
1804
cdef int r
1805
cdef long _cardinality
1806
cdef long cards[MAX_PRIME_DIVISORS] # cardinality of factors
1807
def __init__(self, N):
1808
self.S = ResidueRingModN(N)
1809
self.r = self.S.r # number of prime divisors
1810
self._cardinality = 1
1811
cdef int i
1812
for i in range(self.r):
1813
R = self.S.residue_rings[i]
1814
self.cards[i] = (R.cardinality() + R.cardinality()/R.residue_field().cardinality())
1815
self._cardinality *= self.cards[i]
1816
1817
cpdef long cardinality(self):
1818
return self._cardinality
1819
1820
def __repr__(self):
1821
return "Projective line modulo the ideal %s of norm %s"%(
1822
self.S.N._repr_short(), self.S.N.norm())
1823
1824
## cdef element_to_str(self, p1_element op):
1825
## return '(%s : %s)'%(self.S.element_to_str(op[0]), self.S.element_to_str(op[1]))
1826
1827
cdef element_to_str(self, p1_element op):
1828
cdef ResidueRing_abstract R
1829
v = [ ]
1830
for i in range(self.r):
1831
R = self.S.residue_rings[i]
1832
v.append('(%s : %s)'%(R.element_to_str(op[0][i]), R.element_to_str(op[1][i])))
1833
s = ', '.join(v)
1834
if self.r > 1:
1835
s = '(' + s + ')'
1836
return s
1837
1838
cdef int matrix_action(self, p1_element rop, modn_matrix op0, p1_element op1) except -1:
1839
# op0 = [a,b; c,d]; op1=[u;v]
1840
# product is [a*u+b*v; c*u+d*v]
1841
cdef modn_element t0, t1
1842
self.S.mul(t0, op0[0], op1[0]) # a*u
1843
self.S.mul(t1, op0[1], op1[1]) # b*v
1844
self.S.add(rop[0], t0, t1) # rop[0] = a*u + b*v
1845
self.S.mul(t0, op0[2], op1[0]) # c*u
1846
self.S.mul(t1, op0[3], op1[1]) # d*v
1847
self.S.add(rop[1], t0, t1) # rop[1] = c*u + d*v
1848
1849
cdef void set_element(self, p1_element rop, p1_element op):
1850
self.S.set_element(rop[0], op[0])
1851
self.S.set_element(rop[1], op[1])
1852
1853
######################################################################
1854
# Reducing elements to canonical form, so can compute index
1855
######################################################################
1856
1857
cdef int reduce_element(self, p1_element rop, p1_element op) except -1:
1858
# set rop to the result of reducing op to canonical form
1859
cdef int i
1860
for i in range(self.r):
1861
self.ith_reduce_element(rop, op, i)
1862
1863
cdef int ith_reduce_element(self, p1_element rop, p1_element op, int i) except -1:
1864
# If the ith factor is (a,b), then, as explained in the next_element
1865
# docstring, the normalized form of this element is either:
1866
# (u,1) or (1,v) with v in P.
1867
# The code below is careful to allow for the case when op and rop
1868
# are actually the same object, since this is a standard use case.
1869
cdef residue_element inv, r0, r1
1870
cdef ResidueRing_abstract R = self.S.residue_rings[i]
1871
if R.is_unit(op[1][i]):
1872
# in first case
1873
R.inv(inv, op[1][i])
1874
R.mul(r0, op[0][i], inv)
1875
R.set_element_to_1(r1)
1876
else:
1877
# can't invert b, so must be in second case
1878
R.set_element_to_1(r0)
1879
R.inv(inv, op[0][i])
1880
R.mul(r1, inv, op[1][i])
1881
R.set_element(rop[0][i], r0)
1882
R.set_element(rop[1][i], r1)
1883
return 0
1884
1885
def test_reduce(self):
1886
"""
1887
The test passes if this returns the empty list.
1888
Uses different random numbers each time, so seed the
1889
generator if you want control.
1890
"""
1891
cdef p1_element x, y, z
1892
cdef long a
1893
self.first_element(x)
1894
v = []
1895
from random import randrange
1896
cdef ResidueRing_abstract R
1897
while True:
1898
# get y from x by multiplying through each entry by 3
1899
for i in range(self.r):
1900
R = self.S.residue_rings[i]
1901
a = randrange(1, R.p)
1902
y[0][i][0] = (a*x[0][i][0])%R.n0
1903
y[0][i][1] = (a*x[0][i][1])%R.n1
1904
y[1][i][0] = (a*x[1][i][0])%R.n0
1905
y[1][i][1] = (a*x[1][i][1])%R.n1
1906
self.reduce_element(z, y)
1907
v.append((self.element_to_str(x), self.element_to_str(y), self.element_to_str(z)))
1908
try:
1909
self.next_element(x, x)
1910
except ValueError:
1911
return [w for w in v if w[0] != w[2]]
1912
1913
def test_reduce2(self, int m, int n):
1914
cdef int i
1915
cdef p1_element x, y, z
1916
self.first_element(x)
1917
for i in range(m):
1918
self.next_element(x, x)
1919
print self.element_to_str(x)
1920
cdef ResidueRing_abstract R
1921
for i in range(self.r):
1922
R = self.S.residue_rings[i]
1923
y[0][i][0] = (3*x[0][i][0])%R.n0
1924
y[0][i][1] = (3*x[0][i][1])%R.n1
1925
y[1][i][0] = (3*x[1][i][0])%R.n0
1926
y[1][i][1] = (3*x[1][i][1])%R.n1
1927
print self.element_to_str(y)
1928
from sage.all import cputime
1929
t = cputime()
1930
for i in range(n):
1931
self.reduce_element(z, y)
1932
return cputime(t)
1933
1934
1935
######################################################################
1936
# Standard index of elements
1937
######################################################################
1938
1939
cdef long standard_index(self, p1_element z) except -1:
1940
# return the standard index of the assumed reduced element z of P^1
1941
# The global index of z is got from the local indexes at each factor.
1942
# If we let C_i denote the cardinality of the i-th factor, then the
1943
# global index I is:
1944
# ind(z) = ind(z_0) + ind(z_1)*C_0 + ind(z_2)*C_0*C_1 + ...
1945
cdef int i
1946
cdef long C=1, ind = self.ith_standard_index(z, 0)
1947
for i in range(1, self.r):
1948
C *= self.cards[i-1]
1949
ind += C * self.ith_standard_index(z, i)
1950
return ind
1951
1952
cdef long ith_standard_index(self, p1_element z, int i) except -1:
1953
cdef ResidueRing_abstract R = self.S.residue_rings[i]
1954
# Find standard index of normalized element
1955
# (z[0][i], z[1][i])
1956
# of R x R. The index is defined by the ordering on
1957
# normalized elements given by the next_element method (see
1958
# docs below).
1959
1960
if R.element_is_1(z[1][i]):
1961
# then the index is the index of the first entry
1962
return R.index_of_element(z[0][i])
1963
1964
# The index is the cardinality of R plus the index of z[1][i]
1965
# in the list of multiples of P in R.
1966
return R._cardinality + R.index_of_element_in_P(z[1][i])
1967
1968
######################################################################
1969
# Enumeration of elements
1970
######################################################################
1971
1972
def test_enum(self):
1973
cdef p1_element x
1974
self.first_element(x)
1975
v = []
1976
while True:
1977
c = (self.element_to_str(x), self.standard_index(x))
1978
print c
1979
v.append(c)
1980
try:
1981
self.next_element(x, x)
1982
except ValueError:
1983
return v
1984
1985
def test_enum2(self):
1986
cdef p1_element x
1987
self.first_element(x)
1988
while True:
1989
try:
1990
self.next_element(x, x)
1991
except ValueError:
1992
return
1993
1994
cdef int first_element(self, p1_element rop) except -1:
1995
# set rop to the first standard element, which is 1 in every factor
1996
cdef int i
1997
for i in range(self.r):
1998
# The 2-tuple (0,1) represents the 1 element in all the residue rings.
1999
rop[0][i][0] = 0
2000
rop[0][i][1] = 0
2001
rop[1][i][0] = 1
2002
rop[1][i][1] = 0
2003
return 0
2004
2005
cdef int next_element(self, p1_element rop, p1_element z) except -1:
2006
# set rop equal to the next standard element after z.
2007
# The input z is assumed standard!
2008
# The enumeration is to start with the first ring, enumerate its P^1, and
2009
# if rolls over, go to the next ring, etc.
2010
# At the end, raise ValueError
2011
2012
if rop != z: # not the same C-level pointer
2013
self.set_element(rop, z)
2014
2015
cdef int i=0
2016
while i < self.r and self.next_element_factor(rop, i):
2017
# it rolled over
2018
# Reset i-th one to starting P^1 elt, and increment the (i+1)th
2019
rop[0][i][0] = 0
2020
rop[0][i][1] = 0
2021
rop[1][i][0] = 1
2022
rop[1][i][1] = 0
2023
i += 1
2024
if i == self.r: # we're done
2025
raise ValueError
2026
2027
return 0
2028
2029
cdef bint next_element_factor(self, p1_element op, int i) except -2:
2030
# modify op in place by replacing the element in the i-th P^1 factor by
2031
# the next element. If this involves rolling around, return True; otherwise, False.
2032
2033
cdef ResidueRing_abstract k = self.S.residue_rings[i]
2034
# The P^1 local factor is (a,b) where a = op[0][i] and b = op[1][i].
2035
# Our "abstract" enumeration of the local P^1 with residue ring k is:
2036
# [(a,1) for a in k] U [(1,b) for b in P*k]
2037
if k.element_is_1(op[1][i]): # b == 1
2038
# iterate a
2039
if k.is_last_element(op[0][i]):
2040
# Then next elt is (1,b) where b is the first element of P*k.
2041
# So set b to first element in P, which is 0.
2042
k.set_element_to_0(op[1][i])
2043
# set a to 1
2044
k.set_element_to_1(op[0][i])
2045
else:
2046
k.next_element(op[0][i], op[0][i])
2047
return False # definitely didn't loop around whole P^1
2048
else:
2049
# case when b != 1
2050
if k.is_last_element_in_P(op[1][i]):
2051
# Next element is (1,0) and we return 1 to indicate total loop around
2052
k.set_element_to_0(op[1][i])
2053
return True # looped around
2054
else:
2055
k.next_element_in_P(op[1][i], op[1][i])
2056
return False
2057
2058
# Version using exception handling -- it's about 20% percent slower...
2059
## cdef bint next_element_factor(self, p1_element op, int i):
2060
## # modify op in place by replacing the element in the i-th P^1 factor by
2061
## # the next element. If this involves rolling around, return True; otherwise,
2062
## # return False.
2063
## cdef ResidueRing_abstract k = self.S.residue_rings[i]
2064
## # The P^1 local factor is (a,b) where a = op[0][i] and b = op[1][i].
2065
## # Our "abstract" enumeration of the local P^1 with residue ring k is:
2066
## # [(a,1) for a in k] U [(1,b) for b in P*k]
2067
## if k.element_is_1(op[1][i]): # b == 1
2068
## # iterate a
2069
## try:
2070
## k.next_element(op[0][i], op[0][i])
2071
## except ValueError:
2072
## # Then next elt is (1,b) where b is the first element of P*k.
2073
## # So set b to first element in P, which is 0.
2074
## k.set_element_to_0(op[1][i])
2075
## # set a to 1
2076
## k.set_element_to_1(op[0][i])
2077
## return 0 # definitely didn't loop around whole P^1
2078
## else:
2079
## # case when b != 1
2080
## try:
2081
## k.next_element_in_P(op[1][i], op[1][i])
2082
## except ValueError:
2083
## # Next element is (1,0) and we return 1 to indicate total loop around
2084
## k.set_element_to_0(op[1][i])
2085
## return 1 # looped around
2086
## return 0
2087
2088
2089
# readable version of the function below (not used):
2090
def quaternion_in_terms_of_icosian_basis2(alpha):
2091
"""
2092
Write the quaternion alpha in terms of the fixed basis for integer icosians.
2093
"""
2094
b0 = alpha[0]; b1=alpha[1]; b2=alpha[2]; b3=alpha[3]
2095
a = F.gen()
2096
return [F_two*b0,
2097
(a-F_one)*b0 - b1 + a*b3,
2098
(a-F_one)*b0 + a*b1 - b2,
2099
(-a-F_one)*b0 + a*b2 - b3]
2100
2101
cdef NumberFieldElement_quadratic F_one = F(1), F_two = F(2), F_gen = F.gen(), F_gen_m1=F_gen-F_one, F_gen_m2 = -F_gen-F_one
2102
2103
cpdef object quaternion_in_terms_of_icosian_basis(object alpha):
2104
"""
2105
Write the quaternion alpha in terms of the fixed basis for integer icosians.
2106
"""
2107
# this is a critical path for speed.
2108
cdef NumberFieldElement_quadratic b0 = alpha[0], b1=alpha[1], b2=alpha[2], b3=alpha[3],
2109
return [F_two._mul_(b0), F_gen_m1._mul_(b0)._sub_(b1)._add_(F_gen._mul_(b3)),
2110
F_gen_m1._mul_(b0)._add_(F_gen._mul_(b1))._sub_(b2),
2111
F_gen_m2._mul_(b0)._add_(F_gen._mul_(b2))._sub_(b3)]
2112
2113
2114
####################################################################
2115
# Reduction from Quaternion Algebra mod N.
2116
####################################################################
2117
cdef class ModN_Reduction:
2118
cdef ResidueRingModN S
2119
cdef modn_matrix G[4]
2120
cdef bint is_odd
2121
def __init__(self, N, bint init=True):
2122
cdef int i
2123
2124
if not is_ideal_in_F(N):
2125
raise TypeError, "N must be an ideal of F"
2126
2127
cdef ResidueRingModN S = ResidueRingModN(N)
2128
self.S = S
2129
self.is_odd = (N.norm() % 2 != 0)
2130
2131
if init:
2132
# Set the identity matrix (2 part of this will get partly overwritten when N is even.)
2133
S.set(self.G[0][0], 1); S.set(self.G[0][1], 0); S.set(self.G[0][2], 0); S.set(self.G[0][3], 1)
2134
2135
for i in range(S.r):
2136
self.compute_ith_local_splitting(i)
2137
2138
def reduce_exponent_of_ith_factor(self, i):
2139
"""
2140
Let P be the ith prime factor of the modulus N of self. This function
2141
returns the splitting got by reducing self modulo N/P. See
2142
the documentation for the degeneracy_matrix of
2143
IcosiansModP1ModN for why we wrote this function.
2144
"""
2145
cdef ResidueRing_abstract R = self.S.residue_rings[i]
2146
P = R.P
2147
N2 = self.S.N / P
2148
cdef ModN_Reduction f = ModN_Reduction(N2, init=False)
2149
# We have to set the matrices f.G[i] for i=0,1,2,3, using the
2150
# set_element_reducing_exponent_of_ith_factor method.
2151
cdef int j, k
2152
for j in range(4):
2153
for k in range(4):
2154
self.S.set_element_reducing_exponent_of_ith_factor(f.G[j][k], self.G[j][k], i)
2155
return f
2156
2157
cdef compute_ith_local_splitting(self, int i):
2158
cdef ResidueRingElement z
2159
cdef long m, n
2160
cdef ResidueRing_abstract R = self.S.residue_rings[i]
2161
F = self.S.N.number_field()
2162
2163
if R.p != 2:
2164
# I = [0,-1, 1, 0] works.
2165
R.set_element_to_0(self.G[1][0][i])
2166
R.set_element_to_1(self.G[1][1][i]); R.neg(self.G[1][1][i], self.G[1][1][i])
2167
R.set_element_to_1(self.G[1][2][i])
2168
R.set_element_to_0(self.G[1][3][i])
2169
else:
2170
from sqrt5_fast_python import find_mod2pow_splitting
2171
w = find_mod2pow_splitting(R.e)
2172
for n in range(4):
2173
v = w[n].list()
2174
for m in range(4):
2175
z = v[m]
2176
self.G[n][m][i][0] = z.x[0]
2177
self.G[n][m][i][1] = z.x[1]
2178
return
2179
2180
#######################################################################
2181
# Now find J:
2182
# TODO: *IMPORTANT* -- this must get redone in a way that is
2183
# much faster when the power of the prime is large. Right now
2184
# it does a big enumeration, which is very slow. There is a
2185
# Hensel lift style approach that is dramatically massively
2186
# faster. See the code for find_mod2pow_splitting above,
2187
# which uses an iterative lifting algorithm.
2188
# Must implement this... once everything else is done. This is also slow since my sqrt
2189
# method is ridiculously stupid. A good test is
2190
# sage: time ModN_Reduction(F.primes_above(7)[0]^5)
2191
# CPU times: user 0.65 s, sys: 0.00 s, total: 0.65 s
2192
# which should be instant. And don't try exponent 6, which takes minutes (at least)!
2193
#######################################################################
2194
cdef residue_element a, b, c, d, t, t2, minus_one
2195
found_it = False
2196
R.set_element_to_1(b)
2197
R.coerce_from_nf(minus_one, F(-1))
2198
while not R.is_last_element(b):
2199
# Let c = -1 - b*b
2200
R.mul(t, b, b)
2201
R.mul(t, t, minus_one)
2202
R.add(c, minus_one, t)
2203
if R.is_square(c):
2204
# Next set a = -sqrt(c).
2205
R.sqrt(a, c)
2206
# Set the matrix self.G[2] to [a,b,(j2-a*a)/b,-a]
2207
R.set_element(self.G[2][0][i], a)
2208
R.set_element(self.G[2][1][i], b)
2209
# Set t to (-1-a*a)/b
2210
R.mul(t, a, a)
2211
R.mul(t, t, minus_one)
2212
R.add(t, t, minus_one)
2213
R.inv(t2, b)
2214
R.mul(self.G[2][2][i], t, t2)
2215
R.mul(self.G[2][3][i], a, minus_one)
2216
2217
# Check that indeed we found an independent matrices, over the residue field
2218
good, K = self._matrices_are_independent_mod_ith_prime(i)
2219
if good:
2220
self.S.matrix_mul_ith_factor(self.G[3], self.G[1], self.G[2], i)
2221
return
2222
R.next_element(b, b)
2223
2224
raise NotImplementedError
2225
2226
def _matrices_are_independent_mod_ith_prime(self, int i):
2227
cdef ResidueRing_abstract R = self.S.residue_rings[i]
2228
k = R.residue_field()
2229
M = MatrixSpace(k, 2)
2230
J = M([R.element_to_residue_field(self.G[2][n][i]) for n in range(4)])
2231
I = M([R.element_to_residue_field(self.G[1][n][i]) for n in range(4)])
2232
K = I * J
2233
B = [M.identity_matrix(), I, J, I*J]
2234
V = k**4
2235
W = V.span([x.list() for x in B])
2236
return W.dimension() == 4, K.list()
2237
2238
def __repr__(self):
2239
return 'Reduction modulo %s of norm %s defined by sending I to %s and J to %s'%(
2240
self.S.N._repr_short(), self.S.N.norm(),
2241
self.S.matrix_to_str(self.G[1]), self.S.matrix_to_str(self.G[2]))
2242
2243
cdef int quatalg_to_modn_matrix(self, modn_matrix M, alpha) except -1:
2244
# Given an element alpha in the quaternion algebra, find its image M as a modn_matrix.
2245
cdef modn_element t
2246
cdef modn_element X[4]
2247
cdef int i, j
2248
cdef ResidueRingModN S = self.S
2249
cdef ResidueRing_abstract R
2250
cdef residue_element z[4]
2251
cdef residue_element t2
2252
2253
for i in range(4):
2254
S.coerce_from_nf(X[i], alpha[i], 1)
2255
for i in range(4):
2256
S.mul(M[i], self.G[0][i], X[0])
2257
for j in range(1, 4):
2258
S.mul(t, self.G[j][i], X[j])
2259
S.add(M[i], M[i], t)
2260
2261
if not self.is_odd:
2262
# The first prime is 2, so we have to fix that the above was
2263
# totally wrong at 2.
2264
# TODO: I'm writing this code slowly to work rather
2265
# than quickly, since I'm running out of time.
2266
# Will redo this to be fast later. The algorithm is:
2267
# 1. Write alpha in terms of Icosian ring basis.
2268
# 2. Take the coefficients from *that* linear combination
2269
# and apply the map, just as above, but of course only
2270
# to the first factor of M.
2271
v = quaternion_in_terms_of_icosian_basis(alpha)
2272
# Now v is a list of 4 elements of O_F (unless alpha wasn't in R).
2273
R = self.S.residue_rings[0]
2274
assert R.p == 2, '%s\nBUG: order of factors wrong? '%(self.S.residue_rings,)
2275
for i in range(4):
2276
R.coerce_from_nf(z[i], v[i])
2277
for i in range(4):
2278
R.mul(M[i][0], self.G[0][i][0], z[0])
2279
for j in range(1,4):
2280
R.mul(t2, self.G[j][i][0], z[j])
2281
R.add(M[i][0], M[i][0], t2)
2282
2283
2284
def __call__(self, alpha):
2285
"""
2286
A sort of joke for now. Reduce alpha using this map, then return
2287
string representation...
2288
"""
2289
cdef modn_matrix M
2290
self.quatalg_to_modn_matrix(M, alpha)
2291
return self.S.matrix_to_str(M)
2292
2293
2294
####################################################################
2295
# R^* \ P1(O_F/N)
2296
####################################################################
2297
2298
ctypedef modn_matrix icosian_matrices[120]
2299
2300
cdef class IcosiansModP1ModN:
2301
"""
2302
Create object that represents that set of orbits
2303
(Icosian Group) \ P^1(O_F / N).
2304
2305
EXAMPLES::
2306
2307
sage: from psage.modform.hilbert.sqrt5.sqrt5_fast import F, IcosiansModP1ModN
2308
sage: I = IcosiansModP1ModN(F.prime_above(31)); I
2309
The 2 orbits for the action of the Icosian group on Projective line modulo the ideal (5*a - 2) of norm 31
2310
sage: I = IcosiansModP1ModN(F.prime_above(389)); I
2311
The 7 orbits for the action of the Icosian group on Projective line modulo the ideal (18*a - 5) of norm 389
2312
sage: I = IcosiansModP1ModN(F.prime_above(2011)); I
2313
The 35 orbits for the action of the Icosian group on Projective line modulo the ideal (41*a - 11) of norm 2011
2314
2315
sage: from psage.modform.hilbert.sqrt5.tables import ideals_of_bounded_norm
2316
sage: [len(IcosiansModP1ModN(b)) for b in ideals_of_bounded_norm(300)]
2317
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 3, 3, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 6, 5, 5, 4, 4, 6, 4, 4, 6, 6, 4, 4, 4, 4, 5, 5, 6, 6, 6, 5, 5, 5, 5, 4, 4, 6, 6, 7, 7, 6, 5, 5, 6, 6, 6, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
2318
"""
2319
cdef icosian_matrices G
2320
cdef ModN_Reduction f
2321
cdef ProjectiveLineModN P1
2322
cdef long *std_to_rep_table
2323
cdef long *orbit_reps
2324
cdef long _cardinality
2325
cdef p1_element* orbit_reps_p1elt
2326
def __init__(self, N, f=None, init=True):
2327
# compute choice of splitting, unless one is provided
2328
if f is not None:
2329
self.f = f
2330
else:
2331
self.f = ModN_Reduction(N)
2332
self.P1 = ProjectiveLineModN(N)
2333
self.orbit_reps = <long*>0
2334
self.std_to_rep_table = <long*> sage_malloc(sizeof(long) * self.P1.cardinality())
2335
2336
# TODO: This is very wasteful of memory, by a factor of about 120 on average?
2337
self.orbit_reps_p1elt = <p1_element*>sage_malloc(sizeof(p1_element) * self.P1.cardinality())
2338
2339
# initialize the group G of the 120 mod-N icosian matrices
2340
from sqrt5 import all_icosians
2341
X = all_icosians()
2342
cdef int i
2343
for i in range(len(X)):
2344
self.f.quatalg_to_modn_matrix(self.G[i], X[i])
2345
#print "%s --> %s"%(X[i], self.P1.S.matrix_to_str(self.G[i]))
2346
2347
if init:
2348
self.compute_std_to_rep_table()
2349
2350
def __reduce__(self):
2351
return unpickle_IcosiansModP1ModN_v1, (self.P1.S.N.gens_reduced()[0], )
2352
2353
def __len__(self):
2354
return self._cardinality
2355
2356
def __dealloc__(self):
2357
sage_free(self.std_to_rep_table)
2358
sage_free(self.orbit_reps_p1elt)
2359
if self.orbit_reps:
2360
sage_free(self.orbit_reps)
2361
2362
def __repr__(self):
2363
return "The %s orbits for the action of the Icosian group on %s"%(self._cardinality, self.P1)
2364
2365
def level(self):
2366
"""
2367
Return the level N, which is the ideal we quotiented out by in
2368
constructing this set.
2369
2370
EXAMPLES::
2371
2372
"""
2373
return self.P1.S.N
2374
2375
cpdef compute_std_to_rep_table(self):
2376
# Algorithm is pretty obvious. Just take first element of P1,
2377
# hit by all icosians, making a list of those that are
2378
# equivalent to it. Then find next element of P1 not in the
2379
# first list, etc.
2380
cdef long orbit_cnt
2381
cdef p1_element x, Gx
2382
self.P1.first_element(x)
2383
cdef long ind=0, j, i=0, k
2384
for j in range(self.P1._cardinality):
2385
self.std_to_rep_table[j] = -1
2386
self.std_to_rep_table[0] = 0
2387
orbit_cnt = 1
2388
reps = []
2389
while ind < self.P1._cardinality:
2390
reps.append(ind)
2391
self.P1.set_element(self.orbit_reps_p1elt[i], x)
2392
for j in range(120):
2393
self.P1.matrix_action(Gx, self.G[j], x)
2394
self.P1.reduce_element(Gx, Gx)
2395
k = self.P1.standard_index(Gx)
2396
if self.std_to_rep_table[k] == -1:
2397
self.std_to_rep_table[k] = i
2398
orbit_cnt += 1
2399
else:
2400
# This is a very good test that we got things right. If any
2401
# arithmetic or reduction is wrong, the orbits for the R^*
2402
# "action" are likely to fail to be disjoint.
2403
assert self.std_to_rep_table[k] == i, "Bug: orbits not disjoint"
2404
# This assertion below is also an extremely good test that we got the
2405
# local splittings, etc., right. If any of that goes wrong, then
2406
# the "orbits" have all kinds of random orders.
2407
assert 120 % orbit_cnt == 0, "orbit size = %s must divide 120"%orbit_cnt
2408
orbit_cnt = 0
2409
while ind < self.P1._cardinality and self.std_to_rep_table[ind] != -1:
2410
ind += 1
2411
if ind < self.P1._cardinality:
2412
self.P1.next_element(x, x)
2413
i += 1
2414
self._cardinality = len(reps)
2415
self.orbit_reps = <long*> sage_malloc(sizeof(long)*self._cardinality)
2416
for j in range(self._cardinality):
2417
self.orbit_reps[j] = reps[j]
2418
2419
def compute_std_to_rep_table_debug(self):
2420
# Algorithm is pretty obvious. Just take first element of P1,
2421
# hit by all icosians, making a list of those that are
2422
# equivalent to it. Then find next element of P1 not in the
2423
# first list, etc.
2424
cdef long orbit_cnt
2425
cdef p1_element x, Gx
2426
self.P1.first_element(x)
2427
cdef long ind=0, j, i=0, k
2428
for j in range(self.P1._cardinality):
2429
self.std_to_rep_table[j] = -1
2430
self.std_to_rep_table[0] = 0
2431
orbit_cnt = 1
2432
reps = []
2433
while ind < self.P1._cardinality:
2434
reps.append(ind)
2435
print "Found representative number %s (which has standard index %s): %s"%(
2436
i, ind, self.P1.element_to_str(x))
2437
self.P1.set_element(self.orbit_reps_p1elt[i], x)
2438
for j in range(120):
2439
self.P1.matrix_action(Gx, self.G[j], x)
2440
self.P1.reduce_element(Gx, Gx)
2441
k = self.P1.standard_index(Gx)
2442
if self.std_to_rep_table[k] == -1:
2443
self.std_to_rep_table[k] = i
2444
orbit_cnt += 1
2445
else:
2446
# This is a very good test that we got things right. If any
2447
# arithmetic or reduction is wrong, the orbits for the R^*
2448
# "action" are likely to fail to be disjoint.
2449
assert self.std_to_rep_table[k] == i, "Bug: orbits not disjoint"
2450
# This assertion below is also an extremely good test that we got the
2451
# local splittings, etc., right. If any of that goes wrong, then
2452
# the "orbits" have all kinds of random orders.
2453
assert 120 % orbit_cnt == 0, "orbit size = %s must divide 120"%orbit_cnt
2454
print "It has an orbit of size %s"%orbit_cnt
2455
orbit_cnt = 0
2456
while ind < self.P1._cardinality and self.std_to_rep_table[ind] != -1:
2457
ind += 1
2458
if ind < self.P1._cardinality:
2459
self.P1.next_element(x, x)
2460
i += 1
2461
self._cardinality = len(reps)
2462
self.orbit_reps = <long*> sage_malloc(sizeof(long)*self._cardinality)
2463
for j in range(self._cardinality):
2464
self.orbit_reps[j] = reps[j]
2465
2466
cpdef long cardinality(self):
2467
return self._cardinality
2468
2469
def hecke_matrix(self, P, sparse=True):
2470
"""
2471
Return matrix of Hecke action, acting from the right, so the
2472
i-th row is the image of the i-th standard basis vector.
2473
2474
INPUT:
2475
- P -- prime ideal
2476
- ``sparse`` -- bool (default: True) if True, then a sparse
2477
matrix is returned, otherwise a dense one
2478
2479
OUTPUT:
2480
- a dense or sparse matrix over the rational numbers
2481
2482
NOTE: This function does not cache its results.
2483
2484
EXAMPLES::
2485
2486
sage: from psage.modform.hilbert.sqrt5.sqrt5_fast import F, IcosiansModP1ModN
2487
sage: I = IcosiansModP1ModN(F.primes_above(31)[0]); I
2488
The 2 orbits for the action of the Icosian group on Projective line modulo the ideal (5*a - 2) of norm 31
2489
sage: I.hecke_matrix(F.prime_above(2))
2490
[0 5]
2491
[3 2]
2492
sage: I.hecke_matrix(F.prime_above(3))
2493
[5 5]
2494
[3 7]
2495
sage: I.hecke_matrix(F.prime_above(5))
2496
[1 5]
2497
[3 3]
2498
sage: I.hecke_matrix(F.prime_above(3)).fcp()
2499
(x - 10) * (x - 2)
2500
sage: I.hecke_matrix(F.prime_above(2)).fcp()
2501
(x - 5) * (x + 3)
2502
sage: I.hecke_matrix(F.prime_above(5)).fcp()
2503
(x - 6) * (x + 2)
2504
2505
A bigger example::
2506
2507
sage: I = IcosiansModP1ModN(F.prime_above(389)); I
2508
The 7 orbits for the action of the Icosian group on Projective line modulo the ideal (18*a - 5) of norm 389
2509
sage: t2 = I.hecke_matrix(F.prime_above(2)); t2
2510
[0 3 0 1 1 0 0]
2511
[3 0 0 0 1 0 1]
2512
[0 0 2 1 0 1 1]
2513
[1 0 1 0 1 0 2]
2514
[1 1 0 1 0 1 1]
2515
[0 0 2 0 2 1 0]
2516
[0 1 1 2 1 0 0]
2517
sage: t2.is_sparse()
2518
True
2519
sage: I.hecke_matrix(F.prime_above(2), sparse=False).is_sparse()
2520
False
2521
sage: t3 = I.hecke_matrix(F.prime_above(3))
2522
sage: t5 = I.hecke_matrix(F.prime_above(5))
2523
sage: t11a = I.hecke_matrix(F.primes_above(11)[0])
2524
sage: t11b = I.hecke_matrix(F.primes_above(11)[1])
2525
sage: t2.fcp()
2526
(x - 5) * (x^2 + 5*x + 5) * (x^4 - 3*x^3 - 3*x^2 + 10*x - 4)
2527
sage: t3.fcp()
2528
(x - 10) * (x^2 + 3*x - 9) * (x^4 - 5*x^3 + 3*x^2 + 6*x - 4)
2529
sage: t5.fcp()
2530
(x - 6) * (x^2 + 4*x - 1) * (x^2 - x - 4)^2
2531
sage: t11a.fcp()
2532
(x - 12) * (x + 3)^2 * (x^4 - 17*x^2 + 68)
2533
sage: t11b.fcp()
2534
(x - 12) * (x^2 + 5*x + 5) * (x^4 - x^3 - 23*x^2 + 18*x + 52)
2535
sage: t2*t3 == t3*t2, t2*t5 == t5*t2, t11a*t11b == t11b*t11a
2536
(True, True, True)
2537
2538
Examples involving a prime dividing the level::
2539
2540
sage: from psage.modform.hilbert.sqrt5.sqrt5_fast import F, IcosiansModP1ModN
2541
sage: v = F.primes_above(31); I = IcosiansModP1ModN(v[0])
2542
sage: t31 = I.hecke_matrix(v[1]); t31
2543
[17 15]
2544
[ 9 23]
2545
sage: t31.fcp()
2546
(x - 32) * (x - 8)
2547
sage: t5 = I.hecke_matrix(F.prime_above(5))
2548
sage: t5*t31 == t31*t5
2549
True
2550
sage: I.hecke_matrix(v[0])
2551
Traceback (most recent call last):
2552
...
2553
NotImplementedError: computation of T_P for P dividing the level is not implemented
2554
2555
Another test involving a prime that divides the norm of the level::
2556
2557
sage: v = F.primes_above(809); I = IcosiansModP1ModN(v[0])
2558
sage: t = I.hecke_matrix(v[1])
2559
sage: I
2560
The 14 orbits for the action of the Icosian group on Projective line modulo the ideal (-26*a + 7) of norm 809
2561
sage: t.fcp()
2562
(x - 810) * (x + 46) * (x^4 - 48*x^3 - 1645*x^2 + 65758*x + 617743) * (x^8 + 52*x^7 - 2667*x^6 - 118268*x^5 + 2625509*x^4 + 55326056*x^3 - 1208759312*x^2 + 4911732368*x - 4279699152)
2563
sage: s = I.hecke_matrix(F.prime_above(11))
2564
sage: t*s == s*t
2565
True
2566
"""
2567
if ZZ(self.level().norm()).gcd(ZZ(P.norm())) != 1:
2568
return self._hecke_matrix_badnorm(P, sparse=sparse)
2569
cdef modn_matrix M
2570
cdef p1_element Mx
2571
from sqrt5 import hecke_elements
2572
2573
T = zero_matrix(QQ, self._cardinality, sparse=sparse)
2574
cdef long i, j
2575
2576
for alpha in hecke_elements(P):
2577
self.f.quatalg_to_modn_matrix(M, alpha)
2578
for i in range(self._cardinality):
2579
self.P1.matrix_action(Mx, M, self.orbit_reps_p1elt[i])
2580
self.P1.reduce_element(Mx, Mx)
2581
j = self.std_to_rep_table[self.P1.standard_index(Mx)]
2582
T[i,j] += 1
2583
return T
2584
2585
2586
def _hecke_matrix_badnorm(self, P, sparse=True):
2587
"""
2588
Compute the Hecke matrix for a prime P whose norm divides the
2589
norm of the level.
2590
2591
This function doesn't work if P itself divides the level.
2592
2593
EXAMPLES::
2594
2595
sage: from psage.modform.hilbert.sqrt5.sqrt5_fast import F, IcosiansModP1ModN; v=F.primes_above(71); I=IcosiansModP1ModN(v[0])
2596
sage: I._hecke_matrix_badnorm(v[1])
2597
[61 11]
2598
[55 17]
2599
sage: I._hecke_matrix_badnorm(v[1]).fcp()
2600
(x - 72) * (x - 6)
2601
"""
2602
cdef modn_matrix M, M0
2603
cdef p1_element Mx
2604
from sqrt5 import hecke_elements
2605
2606
T = zero_matrix(QQ, self._cardinality, sparse=sparse)
2607
cdef long i, j
2608
2609
if P.divides(self.level()):
2610
raise NotImplementedError, "computation of T_P for P dividing the level is not implemented"
2611
2612
for beta in hecke_elements(P):
2613
alpha = beta**(-1)
2614
self.f.quatalg_to_modn_matrix(M0, alpha)
2615
if self.P1.S.matrix_is_invertible(M0):
2616
self.P1.S.matrix_inv(M, M0)
2617
for i in range(self._cardinality):
2618
self.P1.matrix_action(Mx, M, self.orbit_reps_p1elt[i])
2619
self.P1.reduce_element(Mx, Mx)
2620
j = self.std_to_rep_table[self.P1.standard_index(Mx)]
2621
T[i,j] += 1
2622
return T
2623
2624
def hecke_operator_on_basis_element(self, P, long i):
2625
"""
2626
Return image of i-th basis vector of self under the Hecke
2627
operator T_P, for P coprime to the level, computed efficiently
2628
in the sense of not computing images of all basis vectors.
2629
2630
INPUT:
2631
- P -- nonzero prime ideal of ring of integers of Q(sqrt(5))
2632
that does not divide the level
2633
- i -- nonnegative integer less than the dimension
2634
2635
OUTPUT:
2636
- a dense vector over the integers
2637
2638
EXAMPLES::
2639
2640
sage: from psage.modform.hilbert.sqrt5.sqrt5_fast import F, IcosiansModP1ModN
2641
sage: v = F.primes_above(31); I = IcosiansModP1ModN(v[0])
2642
sage: I.hecke_matrix(F.prime_above(2))
2643
[0 5]
2644
[3 2]
2645
sage: I.hecke_matrix(v[1])
2646
[17 15]
2647
[ 9 23]
2648
sage: I.hecke_operator_on_basis_element(F.prime_above(2), 0)
2649
(0, 5)
2650
sage: I.hecke_operator_on_basis_element(F.prime_above(2), 1)
2651
(3, 2)
2652
sage: I.hecke_operator_on_basis_element(v[1], 0)
2653
(17, 15)
2654
sage: I.hecke_operator_on_basis_element(v[1], 1)
2655
(9, 23)
2656
"""
2657
cdef modn_matrix M, M0
2658
cdef p1_element Mx
2659
2660
from sqrt5 import hecke_elements
2661
cdef list Q = hecke_elements(P)
2662
v = (ZZ**self._cardinality)(0) # important -- do not use the vector function to make this: see trac 11657.
2663
2664
if ZZ(self.level().norm()).gcd(ZZ(P.norm())) == 1:
2665
for alpha in Q:
2666
self.f.quatalg_to_modn_matrix(M, alpha)
2667
self.P1.matrix_action(Mx, M, self.orbit_reps_p1elt[i])
2668
self.P1.reduce_element(Mx, Mx)
2669
v[self.std_to_rep_table[self.P1.standard_index(Mx)]] += 1
2670
else:
2671
if P.divides(self.level()):
2672
raise NotImplementedError
2673
for beta in Q:
2674
alpha = beta**(-1)
2675
self.f.quatalg_to_modn_matrix(M0, alpha)
2676
if self.P1.S.matrix_is_invertible(M0):
2677
self.P1.S.matrix_inv(M, M0)
2678
self.P1.matrix_action(Mx, M, self.orbit_reps_p1elt[i])
2679
self.P1.reduce_element(Mx, Mx)
2680
v[self.std_to_rep_table[self.P1.standard_index(Mx)]] += 1
2681
return v
2682
2683
2684
def degeneracy_matrix(self, P, sparse=True):
2685
"""
2686
Return degeneracy matrix from level N of self to level N/P.
2687
Here P must be a prime ideal divisor of the ideal N.
2688
"""
2689
#################################################################################
2690
# Important comment / thought regarding this function!
2691
# For this to work, the local splitting maps must be chosen
2692
# in a compatible way. Let R be the icosian ring.
2693
# We compute somehow a map
2694
# R ---> M_2(O/P^n)
2695
# and somehow else, we compute a map
2696
# R ---> M_2(O/P^(n+1)).
2697
# If we are trying to compute the degeneracy map from level P^(n+1)
2698
# to level P^n, and we choose incompatible maps, then our computation
2699
# will simply be WRONG.
2700
# SO WATCH OUT! (This took me like 3 hours of frustration to realize...)
2701
##################################################################################
2702
2703
2704
N = self.P1.S.N
2705
# Figure out the index of P as a prime divisor of N
2706
cdef ResidueRing_abstract R
2707
cdef int k, ind = -1, m = len(self.P1.S.residue_rings)
2708
2709
for k, R in enumerate(self.P1.S.residue_rings):
2710
if R.P == P:
2711
# Got it
2712
ind = k
2713
break
2714
if ind == -1:
2715
raise ValueError, "P must be a prime divisor of the level"
2716
2717
# Compute basis of the P^1 for lower level.
2718
N2 = N/P
2719
2720
# CRITICAL: see above comment. We have to compute the mod-N2
2721
# local splitting map in terms of the fixed choice of map for
2722
# self. If we don't, we will get very subtle bugs.
2723
g = self.f.reduce_exponent_of_ith_factor(ind)
2724
cdef IcosiansModP1ModN I2 = IcosiansModP1ModN(N2, g)
2725
2726
D = zero_matrix(QQ, self._cardinality, I2._cardinality, sparse=sparse)
2727
cdef Py_ssize_t i, j
2728
cdef p1_element x, y
2729
2730
for i in range(self._cardinality):
2731
# Make the p1_element y from self.orbit_reps_p1elt[i]
2732
# by reducing at the the "ind" position
2733
#print "reducing", self.P1.element_to_str(self.orbit_reps_p1elt[i])
2734
self.P1.S.set_element_reducing_exponent_of_ith_factor(
2735
y[0], self.orbit_reps_p1elt[i][0], ind)
2736
self.P1.S.set_element_reducing_exponent_of_ith_factor(
2737
y[1], self.orbit_reps_p1elt[i][1], ind)
2738
I2.P1.reduce_element(x, y)
2739
#print "... to ", I2.P1.element_to_str(x)
2740
j = I2.std_to_rep_table[I2.P1.standard_index(x)]
2741
D[i,j] += 1
2742
2743
return D
2744
2745
def unpickle_IcosiansModP1ModN_v1(x):
2746
import sqrt5
2747
return IcosiansModP1ModN(sqrt5.F.ideal(x))
2748
2749
2750
####################################################################
2751
# fragments/test code below
2752
####################################################################
2753
2754
## def test(v, int n):
2755
## cdef list w = v
2756
## cdef int i
2757
## cdef ResidueRing_abstract R
2758
## for i in range(n):
2759
## R = w[i%3]
2760
2761
2762
## def test(ResidueRing_abstract R):
2763
## cdef modn_element x
2764
## x[0][0] = 5
2765
## x[0][1] = 7
2766
## x[1][0] = 2
2767
## x[1][1] = 8
2768
## R.add(x[2], x[0], x[1])
2769
## print x[2][0], x[2][1]
2770
2771
2772
## def test_kr(long a, long b, long n):
2773
## cdef long i, j=0
2774
## for i in range(n):
2775
## j += kronecker_symbol_si(a,b)
2776
## return j
2777
## def test1():
2778
## cdef int x[6]
2779
## x[2] = 1
2780
## x[3] = 2
2781
## x[4] = 1
2782
## x[5] = 2
2783
## from sqrt5 import F
2784
## Pinert = F.primes_above(7)[0]
2785
## cdef ResidueRing_nonsplit Rinert = ResidueRing(Pinert, 2)
2786
## print (x+2)[0], (x+2)[1]
2787
## Rinert.add(x, x+2, x+4)
2788
## return x[0], x[1], x[2], x[3]
2789
2790
## def bench1(int n):
2791
## from sqrt5 import F
2792
## Pinert = F.primes_above(3)[0]
2793
## Rinert = ResidueRing(Pinert, 2)
2794
## s_inert = Rinert(F.gen(0)+3)
2795
## t = s_inert
2796
## cdef int i
2797
## for i in range(n):
2798
## t = t * s_inert
2799
## return t
2800
2801
## def bench2(int n):
2802
## from sqrt5 import F
2803
## Pinert = F.primes_above(3)[0]
2804
## cdef ResidueRing_nonsplit Rinert = ResidueRing(Pinert, 2)
2805
## cdef ResidueRingElement_nonsplit2 s_inert = Rinert(F.gen(0)+3)
2806
## cdef residue_element t, s
2807
## s[0] = s_inert.x[0]
2808
## s[1] = s_inert.x[1]
2809
## t[0] = s[0]
2810
## t[1] = s[1]
2811
## cdef int i
2812
## for i in range(n):
2813
## Rinert.mul(t, t, s)
2814
## return t[0], t[1]
2815
2816
2817