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