Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/schemes/elliptic_curves/monsky_washnitzer.py
4159 views
1
r"""
2
Computation of Frobenius matrix on Monsky-Washnitzer cohomology
3
4
The most interesting functions to be exported here are
5
:func:`matrix_of_frobenius` and :func:`adjusted_prec`.
6
7
Currently this code is limited to the case `p \geq 5` (no
8
`GF(p^n)` for `n > 1`), and only handles the
9
elliptic curve case (not more general hyperelliptic curves).
10
11
REFERENCES:
12
13
- Kedlaya, K., "Counting points on hyperelliptic curves using
14
Monsky-Washnitzer cohomology", J. Ramanujan Math. Soc. 16 (2001) no
15
4, 323-338
16
17
- Edixhoven, B., "Point counting after Kedlaya", EIDMA-Stieltjes
18
graduate course, Lieden (lecture notes?).
19
20
AUTHORS:
21
22
- David Harvey and Robert Bradshaw: initial code developed at the 2006
23
MSRI graduate workshop, working with Jennifer Balakrishnan and Liang
24
Xiao
25
26
- David Harvey (2006-08): cleaned up, rewrote some chunks, lots more
27
documentation, added Newton iteration method, added more complete
28
'trace trick', integrated better into Sage.
29
30
- David Harvey (2007-02): added algorithm with sqrt(p) complexity
31
(removed in May 2007 due to better C++ implementation)
32
33
- Robert Bradshaw (2007-03): keep track of exact form in reduction
34
algorithms
35
36
- Robert Bradshaw (2007-04): generalization to hyperelliptic curves
37
"""
38
39
#*****************************************************************************
40
# Copyright (C) 2006 William Stein <[email protected]>
41
# 2006 Robert Bradshaw <[email protected]>
42
# 2006 David Harvey <[email protected]>
43
#
44
# Distributed under the terms of the GNU General Public License (GPL)
45
# http://www.gnu.org/licenses/
46
#*****************************************************************************
47
48
49
from sage.rings.all import Integers, Integer, PolynomialRing, is_Polynomial, PowerSeriesRing, Rationals, Rational, LaurentSeriesRing
50
from sage.modules.module import Module
51
from sage.structure.element import ModuleElement
52
from sage.matrix.all import matrix
53
from sage.modules.all import vector
54
from sage.rings.ring import CommutativeAlgebra
55
from sage.structure.element import CommutativeAlgebraElement
56
57
from sage.rings.arith import binomial, integer_ceil as ceil
58
from sage.misc.functional import log
59
from sage.misc.misc import newton_method_sizes
60
61
from ell_generic import is_EllipticCurve
62
from constructor import EllipticCurve
63
64
65
class SpecialCubicQuotientRing(CommutativeAlgebra):
66
r"""
67
Specialised class for representing the quotient ring
68
`R[x,T]/(T - x^3 - ax - b)`, where `R` is an
69
arbitrary commutative base ring (in which 2 and 3 are invertible),
70
`a` and `b` are elements of that ring.
71
72
Polynomials are represented internally in the form
73
`p_0 + p_1 x + p_2 x^2` where the `p_i` are
74
polynomials in `T`. Multiplication of polynomials always
75
reduces high powers of `x` (i.e. beyond `x^2`) to
76
powers of `T`.
77
78
Hopefully this ring is faster than a general quotient ring because
79
it uses the special structure of this ring to speed multiplication
80
(which is the dominant operation in the frobenius matrix
81
calculation). I haven't actually tested this theory though...
82
83
TODO: - Eventually we will want to run this in characteristic 3, so
84
we need to: (a) Allow `Q(x)` to contain an `x^2` term, and
85
(b) Remove the requirement that 3 be invertible. Currently this is
86
used in the Toom-Cook algorithm to speed multiplication.
87
88
EXAMPLES::
89
90
sage: B.<t> = PolynomialRing(Integers(125))
91
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
92
sage: R
93
SpecialCubicQuotientRing over Ring of integers modulo 125 with polynomial T = x^3 + 124*x + 94
94
95
Get generators::
96
97
sage: x, T = R.gens()
98
sage: x
99
(0) + (1)*x + (0)*x^2
100
sage: T
101
(T) + (0)*x + (0)*x^2
102
103
Coercions::
104
105
sage: R(7)
106
(7) + (0)*x + (0)*x^2
107
108
Create elements directly from polynomials::
109
110
sage: A, z = R.poly_ring().objgen()
111
sage: A
112
Univariate Polynomial Ring in T over Ring of integers modulo 125
113
sage: R.create_element(z^2, z+1, 3)
114
(T^2) + (T + 1)*x + (3)*x^2
115
116
Some arithmetic::
117
118
sage: x^3
119
(T + 31) + (1)*x + (0)*x^2
120
sage: 3 * x**15 * T**2 + x - T
121
(3*T^7 + 90*T^6 + 110*T^5 + 20*T^4 + 58*T^3 + 26*T^2 + 124*T) + (15*T^6 + 110*T^5 + 35*T^4 + 63*T^2 + 1)*x + (30*T^5 + 40*T^4 + 8*T^3 + 38*T^2)*x^2
122
123
Retrieve coefficients (output is zero-padded)::
124
125
sage: x^10
126
(3*T^2 + 61*T + 8) + (T^3 + 93*T^2 + 12*T + 40)*x + (3*T^2 + 61*T + 9)*x^2
127
sage: (x^10).coeffs()
128
[[8, 61, 3, 0], [40, 12, 93, 1], [9, 61, 3, 0]]
129
130
TODO: write an example checking multiplication of these polynomials
131
against Sage's ordinary quotient ring arithmetic. I can't seem to
132
get the quotient ring stuff happening right now...
133
"""
134
135
def __init__(self, Q, laurent_series = False):
136
"""
137
Constructor.
138
139
INPUT:
140
141
142
- ``Q`` - a polynomial of the form
143
`Q(x) = x^3 + ax + b`, where `a`, `b` belong to a ring in which
144
2, 3 are invertible.
145
146
- ``laurent_series`` - whether or not to allow
147
negative powers of `T` (default=False)
148
149
150
EXAMPLES::
151
152
sage: B.<t> = PolynomialRing(Integers(125))
153
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
154
sage: R
155
SpecialCubicQuotientRing over Ring of integers modulo 125 with polynomial T = x^3 + 124*x + 94
156
157
::
158
159
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 + 2*t^2 - t + B(1/4))
160
Traceback (most recent call last):
161
...
162
ValueError: Q (=t^3 + 2*t^2 + 124*t + 94) must be of the form x^3 + ax + b
163
164
::
165
166
sage: B.<t> = PolynomialRing(Integers(10))
167
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + 1)
168
Traceback (most recent call last):
169
...
170
ArithmeticError: 2 and 3 must be invertible in the coefficient ring (=Ring of integers modulo 10) of Q
171
"""
172
if not is_Polynomial(Q):
173
raise TypeError, "Q (=%s) must be a polynomial" % Q
174
175
if Q.degree() != 3 or not Q[2].is_zero():
176
raise ValueError, "Q (=%s) must be of the form x^3 + ax + b" % Q
177
178
base_ring = Q.parent().base_ring()
179
180
if not base_ring(6).is_unit():
181
raise ArithmeticError, \
182
"2 and 3 must be invertible in the coefficient ring (=%s) of Q" % \
183
base_ring
184
185
# CommutativeAlgebra.__init__ tries to establish a coercion
186
# from the base ring, by trac ticket #9138. The corresponding
187
# hom set is cached. In order to use self as cache key, its
188
# string representation is used. In otder to get the string
189
# representation, we need to know the attributes _a and
190
# _b. Hence, in #9138, we have to move CommutativeAlgebra.__init__
191
# further down:
192
self._a = Q[1]
193
self._b = Q[0]
194
if laurent_series:
195
self._poly_ring = LaurentSeriesRing(base_ring, 'T') # R[T]
196
else:
197
self._poly_ring = PolynomialRing(base_ring, 'T') # R[T]
198
self._poly_generator = self._poly_ring.gen(0) # the generator T
199
CommutativeAlgebra.__init__(self, base_ring)
200
201
# Precompute a matrix that is used in the Toom-Cook multiplication.
202
# This is where we need 2 and 3 invertible.
203
204
# (a good description of Toom-Cook is online at:
205
# http://www.gnu.org/software/gmp/manual/html_node/Toom-Cook-3-Way-Multiplication.html )
206
207
self._speedup_matrix = \
208
(matrix(Integers(), 3, 3, [2, 4, 8,
209
1, 1, 1,
210
8, 4, 2])**(-1)
211
).change_ring(base_ring).list()
212
213
214
def __repr__(self):
215
"""
216
EXAMPLES::
217
218
sage: B.<t> = PolynomialRing(Integers(125))
219
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
220
sage: print R
221
SpecialCubicQuotientRing over Ring of integers modulo 125 with polynomial T = x^3 + 124*x + 94
222
"""
223
return "SpecialCubicQuotientRing over %s with polynomial T = %s" % \
224
(self.base_ring(), PolynomialRing(self.base_ring(), 'x')(
225
[self._b, self._a, 0, 1]))
226
227
def poly_ring(self):
228
"""
229
Return the underlying polynomial ring in `T`.
230
231
EXAMPLES::
232
233
sage: B.<t> = PolynomialRing(Integers(125))
234
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
235
sage: R.poly_ring()
236
Univariate Polynomial Ring in T over Ring of integers modulo 125
237
"""
238
return self._poly_ring
239
240
def gens(self):
241
"""
242
Return a list [x, T] where x and T are the generators of the ring
243
(as element *of this ring*).
244
245
.. note::
246
247
I have no idea if this is compatible with the usual Sage
248
'gens' interface.
249
250
EXAMPLES::
251
252
sage: B.<t> = PolynomialRing(Integers(125))
253
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
254
sage: x, T = R.gens()
255
sage: x
256
(0) + (1)*x + (0)*x^2
257
sage: T
258
(T) + (0)*x + (0)*x^2
259
"""
260
return [SpecialCubicQuotientRingElement(self,
261
self._poly_ring(0), self._poly_ring(1), self._poly_ring(0),
262
check=False),
263
SpecialCubicQuotientRingElement(self,
264
self._poly_generator, self._poly_ring(0), self._poly_ring(0),
265
check=False)]
266
267
def create_element(self, p0, p1, p2, check=True):
268
"""
269
Creates the element `p_0 + p_1*x + p_2*x^2`, where the `p_i`
270
are polynomials in `T`.
271
272
INPUT:
273
274
275
- ``p0, p1, p2`` - coefficients; must be coercible
276
into poly_ring()
277
278
- ``check`` - bool (default True): whether to carry
279
out coercion
280
281
282
EXAMPLES::
283
284
sage: B.<t> = PolynomialRing(Integers(125))
285
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
286
sage: A, z = R.poly_ring().objgen()
287
sage: R.create_element(z^2, z+1, 3)
288
(T^2) + (T + 1)*x + (3)*x^2
289
"""
290
return SpecialCubicQuotientRingElement(self, p0, p1, p2, check)
291
292
293
def __call__(self, value):
294
"""
295
EXAMPLES::
296
297
sage: B.<t> = PolynomialRing(Integers(125))
298
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
299
sage: R(3)
300
(3) + (0)*x + (0)*x^2
301
"""
302
return self._coerce_(value)
303
304
305
def _coerce_impl(self, value):
306
"""
307
EXAMPLES::
308
309
sage: B.<t> = PolynomialRing(Integers(125))
310
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
311
sage: R._coerce_impl(3)
312
(3) + (0)*x + (0)*x^2
313
"""
314
# coerce to underlying polynomial ring (possibly via base ring):
315
value = self._poly_ring._coerce_(value)
316
317
return SpecialCubicQuotientRingElement(self,
318
value, self._poly_ring(0), self._poly_ring(0), check=False)
319
320
321
322
323
class SpecialCubicQuotientRingElement(CommutativeAlgebraElement):
324
"""
325
An element of a SpecialCubicQuotientRing.
326
"""
327
328
def __init__(self, parent, p0, p1, p2, check=True):
329
"""
330
Constructs the element `p_0 + p_1*x + p_2*x^2`, where
331
the `p_i` are polynomials in `T`.
332
333
INPUT:
334
335
336
- ``parent`` - a SpecialCubicQuotientRing
337
338
- ``p0, p1, p2`` - coefficients; must be coercible
339
into parent.poly_ring()
340
341
- ``check`` - bool (default True): whether to carry
342
out coercion
343
344
345
EXAMPLES::
346
347
sage: B.<t> = PolynomialRing(Integers(125))
348
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
349
sage: from sage.schemes.elliptic_curves.monsky_washnitzer import SpecialCubicQuotientRingElement
350
sage: SpecialCubicQuotientRingElement(R, 2, 3, 4)
351
(2) + (3)*x + (4)*x^2
352
"""
353
if not isinstance(parent, SpecialCubicQuotientRing):
354
raise TypeError, \
355
"parent (=%s) must be a SpecialCubicQuotientRing" % parent
356
357
CommutativeAlgebraElement.__init__(self, parent)
358
359
if check:
360
poly_ring = parent.poly_ring()
361
p0 = poly_ring(p0)
362
p1 = poly_ring(p1)
363
p2 = poly_ring(p2)
364
365
self._triple = (p0, p1, p2)
366
367
def coeffs(self):
368
"""
369
Returns list of three lists of coefficients, corresponding to the
370
`x^0`, `x^1`, `x^2` coefficients. The lists
371
are zero padded to the same length. The list entries belong to the
372
base ring.
373
374
EXAMPLES::
375
376
sage: B.<t> = PolynomialRing(Integers(125))
377
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
378
sage: p = R.create_element(t, t^2 - 2, 3)
379
sage: p.coeffs()
380
[[0, 1, 0], [123, 0, 1], [3, 0, 0]]
381
"""
382
coeffs = [column.coeffs() for column in self._triple]
383
degree = max([len(x) for x in coeffs])
384
base_ring = self.parent().base_ring()
385
for column in coeffs:
386
column.extend([base_ring(0)] * (degree - len(column)))
387
return coeffs
388
389
def __nonzero__(self):
390
"""
391
EXAMPLES::
392
393
sage: B.<t> = PolynomialRing(Integers(125))
394
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
395
sage: x, T = R.gens()
396
sage: not x
397
False
398
sage: not T
399
False
400
sage: not R.create_element(0, 0, 0)
401
True
402
"""
403
return not not self._triple[0] or not not self._triple[1] or not not self._triple[2]
404
405
def __cmp__(self, other):
406
"""
407
EXAMPLES::
408
409
sage: B.<t> = PolynomialRing(Integers(125))
410
sage: x, t = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4)).gens()
411
sage: x == t
412
False
413
sage: x == x
414
True
415
sage: x == x + x - x
416
True
417
"""
418
return cmp(self._triple, other._triple)
419
420
def _repr_(self):
421
"""
422
EXAMPLES::
423
424
sage: B.<t> = PolynomialRing(Integers(125))
425
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
426
sage: x, T = R.gens()
427
sage: x + T*x - 2*T^2
428
(123*T^2) + (T + 1)*x + (0)*x^2
429
"""
430
return "(%s) + (%s)*x + (%s)*x^2" % self._triple
431
432
433
def _latex_(self):
434
"""
435
EXAMPLES::
436
437
sage: B.<t> = PolynomialRing(Integers(125))
438
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
439
sage: x, T = R.gens()
440
sage: f = x + T*x - 2*T^2
441
sage: latex(f)
442
(123 T^{2}) + (T + 1)x + (0)x^2
443
"""
444
return "(%s) + (%s)x + (%s)x^2" % \
445
tuple([column._latex_() for column in self._triple])
446
447
448
def _add_(self, other):
449
"""
450
EXAMPLES::
451
452
sage: B.<t> = PolynomialRing(Integers(125))
453
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
454
sage: f = R.create_element(2, t, t^2 - 3)
455
sage: g = R.create_element(3 + t, -t, t)
456
sage: f + g
457
(T + 5) + (0)*x + (T^2 + T + 122)*x^2
458
"""
459
return SpecialCubicQuotientRingElement(self.parent(),
460
self._triple[0] + other._triple[0],
461
self._triple[1] + other._triple[1],
462
self._triple[2] + other._triple[2],
463
check=False)
464
465
466
def _sub_(self, other):
467
"""
468
EXAMPLES::
469
470
sage: B.<t> = PolynomialRing(Integers(125))
471
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
472
sage: f = R.create_element(2, t, t^2 - 3)
473
sage: g = R.create_element(3 + t, -t, t)
474
sage: f - g
475
(124*T + 124) + (2*T)*x + (T^2 + 124*T + 122)*x^2
476
"""
477
return SpecialCubicQuotientRingElement(self.parent(),
478
self._triple[0] - other._triple[0],
479
self._triple[1] - other._triple[1],
480
self._triple[2] - other._triple[2],
481
check=False)
482
483
484
def shift(self, n):
485
"""
486
Returns this element multiplied by `T^n`.
487
488
EXAMPLES::
489
490
sage: B.<t> = PolynomialRing(Integers(125))
491
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
492
sage: f = R.create_element(2, t, t^2 - 3)
493
sage: f
494
(2) + (T)*x + (T^2 + 122)*x^2
495
sage: f.shift(1)
496
(2*T) + (T^2)*x + (T^3 + 122*T)*x^2
497
sage: f.shift(2)
498
(2*T^2) + (T^3)*x + (T^4 + 122*T^2)*x^2
499
"""
500
return SpecialCubicQuotientRingElement(self.parent(),
501
self._triple[0].shift(n),
502
self._triple[1].shift(n),
503
self._triple[2].shift(n),
504
check=False)
505
506
507
def scalar_multiply(self, scalar):
508
"""
509
Multiplies this element by a scalar, i.e. just multiply each
510
coefficient of `x^j` by the scalar.
511
512
INPUT:
513
514
515
- ``scalar`` - either an element of base_ring, or an
516
element of poly_ring.
517
518
519
EXAMPLES::
520
521
sage: B.<t> = PolynomialRing(Integers(125))
522
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
523
sage: x, T = R.gens()
524
sage: f = R.create_element(2, t, t^2 - 3)
525
sage: f
526
(2) + (T)*x + (T^2 + 122)*x^2
527
sage: f.scalar_multiply(2)
528
(4) + (2*T)*x + (2*T^2 + 119)*x^2
529
sage: f.scalar_multiply(t)
530
(2*T) + (T^2)*x + (T^3 + 122*T)*x^2
531
"""
532
scalar = self.parent()._poly_ring(scalar)
533
return SpecialCubicQuotientRingElement(self.parent(),
534
scalar * self._triple[0],
535
scalar * self._triple[1],
536
scalar * self._triple[2],
537
check=False)
538
539
def square(self):
540
"""
541
EXAMPLES::
542
543
sage: B.<t> = PolynomialRing(Integers(125))
544
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
545
sage: x, T = R.gens()
546
547
::
548
549
sage: f = R.create_element(1 + 2*t + 3*t^2, 4 + 7*t + 9*t^2, 3 + 5*t + 11*t^2)
550
sage: f.square()
551
(73*T^5 + 16*T^4 + 38*T^3 + 39*T^2 + 70*T + 120) + (121*T^5 + 113*T^4 + 73*T^3 + 8*T^2 + 51*T + 61)*x + (18*T^4 + 60*T^3 + 22*T^2 + 108*T + 31)*x^2
552
"""
553
return self * self
554
555
556
def _mul_(self, other):
557
"""
558
EXAMPLES::
559
560
sage: B.<t> = PolynomialRing(Integers(125))
561
sage: R = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4))
562
sage: x, T = R.gens()
563
564
::
565
566
sage: f = R.create_element(1 + 2*t + 3*t^2, 4 + 7*t + 9*t^2, 3 + 5*t + 11*t^2)
567
sage: g = R.create_element(4 + 3*t + 7*t^2, 2 + 3*t + t^2, 8 + 4*t + 6*t^2)
568
sage: f * g
569
(65*T^5 + 27*T^4 + 33*T^3 + 75*T^2 + 120*T + 57) + (66*T^5 + T^4 + 123*T^3 + 95*T^2 + 24*T + 50)*x + (45*T^4 + 75*T^3 + 37*T^2 + 2*T + 52)*x^2
570
"""
571
if not isinstance(other, SpecialCubicQuotientRingElement):
572
return self.scalar_multiply(other)
573
574
# Here we do Toom-Cook three-way multiplication, which reduces the
575
# naive 9 polynomial multiplications to only 5 polynomial multiplications.
576
577
a0, a1, a2 = self._triple
578
b0, b1, b2 = other._triple
579
M = self.parent()._speedup_matrix
580
581
if self is other:
582
# faster method if we're squaring
583
p0 = a0 * a0
584
temp = a0 + 2*a1 + 4*a2
585
p1 = temp * temp
586
temp = a0 + a1 + a2
587
p2 = temp * temp
588
temp = 4*a0 + 2*a1 + a2
589
p3 = temp * temp
590
p4 = a2 * a2
591
592
else:
593
p0 = a0 * b0
594
p1 = (a0 + 2*a1 + 4*a2) * (b0 + 2*b1 + 4*b2)
595
p2 = (a0 + a1 + a2) * (b0 + b1 + b2)
596
p3 = (4*a0 + 2*a1 + a2) * (4*b0 + 2*b1 + b2)
597
p4 = a2 * b2
598
599
q1 = p1 - p0 - 16*p4
600
q2 = p2 - p0 - p4
601
q3 = p3 - 16*p0 - p4
602
603
c0 = p0
604
c1 = M[0]*q1 + M[1]*q2 + M[2]*q3
605
c2 = M[3]*q1 + M[4]*q2 + M[5]*q3
606
c3 = M[6]*q1 + M[7]*q2 + M[8]*q3
607
c4 = p4
608
609
# Now the product is c0 + c1 x + c2 x^2 + c3 x^3 + c4 x^4.
610
# We need to reduce mod y = x^3 + ax + b and return result.
611
612
parent = self.parent()
613
T = parent._poly_generator
614
b = parent._b
615
a = parent._a
616
617
# todo: These lines are necessary to get binop stuff working
618
# for certain base rings, e.g. when we compute b*c3 in the
619
# final line. They shouldn't be necessary. Need to fix this
620
# somewhere else in Sage.
621
a = parent._poly_ring(a)
622
b = parent._poly_ring(b)
623
624
return SpecialCubicQuotientRingElement(parent,
625
-b*c3 + c0 + c3*T,
626
-b*c4 - a*c3 + c1 + c4*T,
627
-a*c4 + c2,
628
check=False)
629
630
def transpose_list(input):
631
"""
632
INPUT:
633
634
635
- ``input`` - a list of lists, each list of the same
636
length
637
638
639
OUTPUT:
640
641
642
- ``output`` - a list of lists such that output[i][j]
643
= input[j][i]
644
645
646
EXAMPLES::
647
648
sage: from sage.schemes.elliptic_curves.monsky_washnitzer import transpose_list
649
sage: L = [[1, 2], [3, 4], [5, 6]]
650
sage: transpose_list(L)
651
[[1, 3, 5], [2, 4, 6]]
652
"""
653
654
h = len(input)
655
w = len(input[0])
656
657
output = []
658
for i in range(w):
659
row = []
660
for j in range(h):
661
row.append(input[j][i])
662
output.append(row)
663
return output
664
665
666
667
def helper_matrix(Q):
668
"""
669
Computes the (constant) matrix used to calculate the linear
670
combinations of the `d(x^i y^j)` needed to eliminate the
671
negative powers of `y` in the cohomology (i.e. in
672
reduce_negative()).
673
674
INPUT:
675
676
677
- ``Q`` - cubic polynomial
678
"""
679
a = Q[1]
680
b = Q[0]
681
682
# Discriminant (should be invertible for a curve of good reduction)
683
D = 4*a**3 + 27*b**2
684
Dinv = D**(-1) # NB do not use 1/D
685
686
# This is the inverse of the matrix
687
# [ a, -3b, 0 ]
688
# [ 0, -2a, -3b ]
689
# [ 3, 0, -2a ]
690
691
M = Dinv * matrix( [ [4*a**2 , -6*b*a , 9*b**2 ],
692
[-9*b , -2*a**2 , 3*b*a ],
693
[ 6*a , -9*b , -2*a**2 ] ])
694
return M
695
696
def lift(x):
697
r"""
698
Tries to call x.lift(), presumably from the `p`-adics to ZZ.
699
700
If this fails, it assumes the input is a power series, and tries to
701
lift it to a power series over QQ.
702
703
This function is just a very kludgy solution to the problem of
704
trying to make the reduction code (below) work over both Zp and
705
Zp[[t]].
706
"""
707
try:
708
return x.lift()
709
except AttributeError:
710
return PowerSeriesRing(Rationals(), "t")(x.list(), x.prec())
711
712
713
714
def reduce_negative(Q, p, coeffs, offset, exact_form=None):
715
"""
716
Applies cohomology relations to incorporate negative powers of
717
`y` into the `y^0` term.
718
719
INPUT:
720
721
722
- ``p`` - prime
723
724
- ``Q`` - cubic polynomial
725
726
- ``coeffs`` - list of length 3 lists. The
727
`i^{th}` list [a, b, c] represents
728
`y^{2(i - offset)} (a + bx + cx^2) dx/y`.
729
730
- ``offset`` - nonnegative integer
731
732
733
OUTPUT: The reduction is performed in-place. The output is placed
734
in coeffs[offset]. Note that coeffs[i] will be meaningless for i
735
offset after this function is finished.
736
737
EXAMPLE::
738
739
sage: R.<x> = Integers(5^3)['x']
740
sage: Q = x^3 - x + R(1/4)
741
sage: coeffs = [[10, 15, 20], [1, 2, 3], [4, 5, 6], [7, 8, 9]]
742
sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs]
743
sage: monsky_washnitzer.reduce_negative(Q, 5, coeffs, 3)
744
sage: coeffs[3]
745
[28, 52, 9]
746
747
::
748
749
sage: R.<x> = Integers(7^3)['x']
750
sage: Q = x^3 - x + R(1/4)
751
sage: coeffs = [[7, 14, 21], [1, 2, 3], [4, 5, 6], [7, 8, 9]]
752
sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs]
753
sage: monsky_washnitzer.reduce_negative(Q, 7, coeffs, 3)
754
sage: coeffs[3]
755
[245, 332, 9]
756
"""
757
758
m = helper_matrix(Q).list()
759
base_ring = Q.base_ring()
760
next_a = coeffs[0]
761
762
if exact_form is not None:
763
x = exact_form.parent().gen(0)
764
y = exact_form.parent()(exact_form.parent().base_ring().gen(0))
765
766
try:
767
three_j_plus_5 = 5 - base_ring(6*offset)
768
three_j_plus_7 = 7 - base_ring(6*offset)
769
six = base_ring(6)
770
771
for i in range(0, offset):
772
773
j = 2*(i-offset)
774
a = next_a
775
next_a = coeffs[i+1]
776
777
# todo: the following divisions will sometimes involve
778
# a division by (a power of) p. In all cases, we know (from
779
# Kedlaya's estimates) that the answer should be p-integral.
780
# However, since we're working over $Z/p^k Z$, we're not allowed
781
# to "divide by p". So currently we lift to Q, divide, and coerce
782
# back. Eventually, when pAdicInteger is implemented, and plays
783
# nicely with pAdicField, we should reimplement this stuff
784
# using pAdicInteger.
785
786
if (p.divides(j+1)):
787
# need to lift here to perform the division
788
a[0] = base_ring(lift(a[0]) / (j+1))
789
a[1] = base_ring(lift(a[1]) / (j+1))
790
a[2] = base_ring(lift(a[2]) / (j+1))
791
else:
792
j_plus_1_inv = ~base_ring(j+1)
793
a[0] = a[0] * j_plus_1_inv
794
a[1] = a[1] * j_plus_1_inv
795
a[2] = a[2] * j_plus_1_inv
796
797
c1 = m[3]*a[0] + m[4]*a[1] + m[5]*a[2]
798
c2 = m[6]*a[0] + m[7]*a[1] + m[8]*a[2]
799
next_a[0] = next_a[0] - three_j_plus_5 * c1
800
next_a[1] = next_a[1] - three_j_plus_7 * c2
801
802
three_j_plus_7 = three_j_plus_7 + six
803
three_j_plus_5 = three_j_plus_5 + six
804
805
if exact_form is not None:
806
c0 = m[0]*a[0] + m[1]*a[1] + m[2]*a[2]
807
exact_form += (c0 + c1*x + c2 * x**2) * y**(j+1)
808
809
810
except NotImplementedError:
811
raise NotImplementedError, \
812
"It looks like you've found a non-integral matrix of Frobenius! " \
813
"(Q=%s, p=%s)\nTime to write a paper." % (Q, p)
814
815
coeffs[int(offset)] = next_a
816
817
return exact_form
818
819
820
821
def reduce_positive(Q, p, coeffs, offset, exact_form=None):
822
"""
823
Applies cohomology relations to incorporate positive powers of
824
`y` into the `y^0` term.
825
826
INPUT:
827
828
829
- ``Q`` - cubic polynomial
830
831
- ``coeffs`` - list of length 3 lists. The
832
`i^{th}` list [a, b, c] represents
833
`y^{2(i - offset)} (a + bx + cx^2) dx/y`.
834
835
- ``offset`` - nonnegative integer
836
837
838
OUTPUT: The reduction is performed in-place. The output is placed
839
in coeffs[offset]. Note that coeffs[i] will be meaningless for i
840
offset after this function is finished.
841
842
EXAMPLE::
843
844
sage: R.<x> = Integers(5^3)['x']
845
sage: Q = x^3 - x + R(1/4)
846
847
::
848
849
sage: coeffs = [[1, 2, 3], [10, 15, 20]]
850
sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs]
851
sage: monsky_washnitzer.reduce_positive(Q, 5, coeffs, 0)
852
sage: coeffs[0]
853
[16, 102, 88]
854
855
::
856
857
sage: coeffs = [[9, 8, 7], [10, 15, 20]]
858
sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs]
859
sage: monsky_washnitzer.reduce_positive(Q, 5, coeffs, 0)
860
sage: coeffs[0]
861
[24, 108, 92]
862
"""
863
864
base_ring = Q.base_ring()
865
next_a = coeffs[len(coeffs) - 1]
866
867
Qa = Q[1]
868
Qb = Q[0]
869
870
A = 2*Qa
871
B = 3*Qb
872
873
offset = Integer(offset)
874
875
876
if exact_form is not None:
877
x = exact_form.parent().gen(0)
878
y = exact_form.parent().base_ring().gen(0)
879
# y = exact_form.parent()(exact_form.parent().base_ring().gen(0))
880
881
for i in range(len(coeffs)-1, offset, -1):
882
j = 2*(i-offset) - 2
883
a = next_a
884
next_a = coeffs[i-1]
885
886
a[0] = a[0] - Qa*a[2]/3 # subtract d(y^j + 3)
887
if exact_form is not None:
888
exact_form += Q.base_ring()(a[2].lift() / (3*j+9)) * y**(j+3)
889
890
# todo: see comments about pAdicInteger in reduceNegative()
891
892
# subtract off c1 of d(x y^j + 1), and
893
if p.divides(3*j + 5):
894
c1 = base_ring(lift(a[0]) / (3*j + 5))
895
else:
896
c1 = a[0] / (3*j + 5)
897
898
# subtract off c2 of d(x^2 y^j + 1)
899
if p.divides(3*j + 7):
900
c2 = base_ring(lift(a[1]) / (3*j + 7))
901
else:
902
c2 = a[1] / (3*j + 7)
903
904
next_a[0] = next_a[0] + B*c1*(j+1)
905
next_a[1] = next_a[1] + A*c1*(j+1) + B*c2*(j+1)
906
next_a[2] = next_a[2] + A*c2*(j+1)
907
908
if exact_form is not None:
909
exact_form += (c1*x + c2 * x**2) * y**(j+1)
910
911
coeffs[int(offset)] = next_a
912
913
return exact_form
914
915
916
917
def reduce_zero(Q, coeffs, offset, exact_form=None):
918
"""
919
Applies cohomology relation to incorporate `x^2 y^0` term
920
into `x^0 y^0` and `x^1 y^0` terms.
921
922
INPUT:
923
924
925
- ``Q`` - cubic polynomial
926
927
- ``coeffs`` - list of length 3 lists. The
928
`i^{th}` list [a, b, c] represents
929
`y^{2(i - offset)} (a + bx + cx^2) dx/y`.
930
931
- ``offset`` - nonnegative integer
932
933
934
OUTPUT: The reduction is performed in-place. The output is placed
935
in coeffs[offset]. This method completely ignores coeffs[i] for i
936
!= offset.
937
938
EXAMPLE::
939
940
sage: R.<x> = Integers(5^3)['x']
941
sage: Q = x^3 - x + R(1/4)
942
sage: coeffs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
943
sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs]
944
sage: monsky_washnitzer.reduce_zero(Q, coeffs, 1)
945
sage: coeffs[1]
946
[6, 5, 0]
947
"""
948
949
a = coeffs[int(offset)]
950
if a[2] == 0:
951
return exact_form
952
953
Qa = Q[1]
954
955
a[0] = a[0] - a[2]*Qa/3 # $3x^2 dx/y = -a dx/y$
956
957
coeffs[int(offset)] = a
958
959
if exact_form is not None:
960
x = exact_form.parent().gen(0)
961
y = exact_form.parent()(exact_form.parent().base_ring().gen(0))
962
exact_form += Q.base_ring()(a[2] / 3) * y
963
964
a[2] = 0
965
966
coeffs[int(offset)] = a
967
return exact_form
968
969
970
971
def reduce_all(Q, p, coeffs, offset, compute_exact_form=False):
972
"""
973
Applies cohomology relations to reduce all terms to a linear
974
combination of `dx/y` and `x dx/y`.
975
976
INPUT:
977
978
979
- ``Q`` - cubic polynomial
980
981
- ``coeffs`` - list of length 3 lists. The
982
`i^{th}` list [a, b, c] represents
983
`y^{2(i - offset)} (a + bx + cx^2) dx/y`.
984
985
- ``offset`` - nonnegative integer
986
987
988
OUTPUT:
989
990
991
- ``A, B`` - pair such that the input differential is
992
cohomologous to (A + Bx) dx/y.
993
994
995
.. note::
996
997
The algorithm operates in-place, so the data in coeffs is
998
destroyed.
999
1000
EXAMPLE::
1001
1002
sage: R.<x> = Integers(5^3)['x']
1003
sage: Q = x^3 - x + R(1/4)
1004
sage: coeffs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
1005
sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs]
1006
sage: monsky_washnitzer.reduce_all(Q, 5, coeffs, 1)
1007
(21, 106)
1008
"""
1009
1010
R = Q.base_ring()
1011
1012
if compute_exact_form:
1013
# exact_form = SpecialCubicQuotientRing(Q, laurent_series=True)(0)
1014
exact_form = PolynomialRing(LaurentSeriesRing(Q.base_ring(), 'y'), 'x')(0)
1015
# t = (Q.base_ring().order().factor())[0]
1016
# from sage.rings.padics.qp import pAdicField
1017
# exact_form = PolynomialRing(LaurentSeriesRing(pAdicField(p, t[1]), 'y'), 'x')(0)
1018
else:
1019
exact_form = None
1020
1021
while len(coeffs) <= offset:
1022
coeffs.append([R(0), R(0), R(0)])
1023
1024
exact_form = reduce_negative(Q, p, coeffs, offset, exact_form)
1025
exact_form = reduce_positive(Q, p, coeffs, offset, exact_form)
1026
exact_form = reduce_zero(Q, coeffs, offset, exact_form)
1027
1028
if exact_form is None:
1029
return coeffs[int(offset)][0], coeffs[int(offset)][1]
1030
else:
1031
return (coeffs[int(offset)][0], coeffs[int(offset)][1]), exact_form
1032
1033
1034
1035
def frobenius_expansion_by_newton(Q, p, M):
1036
r"""
1037
Computes the action of Frobenius on `dx/y` and on
1038
`x dx/y`, using Newton's method (as suggested in Kedlaya's
1039
paper).
1040
1041
(This function does *not* yet use the cohomology relations - that
1042
happens afterwards in the "reduction" step.)
1043
1044
More specifically, it finds `F_0` and `F_1` in
1045
the quotient ring `R[x, T]/(T - Q(x))`, such that
1046
1047
.. math::
1048
1049
F( dx/y) = T^{-r} F0 dx/y, \text{\ and\ } F(x dx/y) = T^{-r} F1 dx/y
1050
1051
where
1052
1053
.. math::
1054
1055
r = ( (2M-3)p - 1 )/2.
1056
1057
1058
(Here `T` is `y^2 = z^{-2}`, and `R` is the
1059
coefficient ring of `Q`.)
1060
1061
`F_0` and `F_1` are computed in the
1062
SpecialCubicQuotientRing associated to `Q`, so all powers
1063
of `x^j` for `j \geq 3` are reduced to powers of
1064
`T`.
1065
1066
INPUT:
1067
1068
1069
- ``Q`` - cubic polynomial of the form
1070
`Q(x) = x^3 + ax + b`, whose coefficient ring is a
1071
`Z/(p^M)Z`-algebra
1072
1073
- ``p`` - residue characteristic of the p-adic field
1074
1075
- ``M`` - p-adic precision of the coefficient ring
1076
(this will be used to determine the number of Newton iterations)
1077
1078
1079
OUTPUT:
1080
1081
1082
- ``F0, F1`` - elements of
1083
SpecialCubicQuotientRing(Q), as described above
1084
1085
- ``r`` - non-negative integer, as described above
1086
"""
1087
1088
S = SpecialCubicQuotientRing(Q)
1089
x, _ = S.gens() # T = y^2
1090
base_ring = S.base_ring()
1091
1092
# When we compute Frob(1/y) we actually only need precision M-1, since
1093
# we're going to multiply by p at the end anyway.
1094
M = float(M - 1)
1095
1096
# Kedlaya sets s = Q(x^p)/T^p = 1 + p T^{-p} E, where
1097
# E = (Q(x^p) - Q(x)^p) / p (has integral coefficients).
1098
# Then he computes s^{-1/2} in S, using Newton's method to find
1099
# successive approximations. We follow this plan, but we normalise our
1100
# approximations so that we only ever need positive powers of T.
1101
1102
# Start by setting r = Q(x^p)/2 = 1/2 T^p s.
1103
# (The 1/2 is for convenience later on.)
1104
x_to_p_less_one = x**(p-1)
1105
x_to_p = x_to_p_less_one * x
1106
x_to_p_cubed = x_to_p.square() * x_to_p
1107
r = (base_ring(1) / base_ring(2)) * (x_to_p_cubed + Q[1]*x_to_p + S(Q[0]))
1108
1109
# todo: this next loop would be clearer if it used the newton_method_sizes()
1110
# function
1111
1112
# We will start with a hard-coded initial approximation, which we provide
1113
# up to precision 3. First work out what precision is best to start with.
1114
if M <= 3:
1115
initial_precision = M
1116
elif ceil(log(M/2, 2)) == ceil(log(M/3, 2)):
1117
# In this case there's no advantage to starting with precision three,
1118
# because we'll overshoot at the end. E.g. suppose the final precision
1119
# is 8. If we start with precision 2, we need two iterations to get us
1120
# to 8. If we start at precision 3, we will still need two iterations,
1121
# but we do more work along the way. So may as well start with only 2.
1122
initial_precision = 2
1123
else:
1124
initial_precision = 3
1125
1126
# Now compute the first approximation. In the main loop below, X is the
1127
# normalised approximation, and k is the precision. More specifically,
1128
# X = T^{p(k-1)} x_i, where x_i is an approximation to s^{-1/2}, and the
1129
# approximation is correct mod p^k.
1130
if initial_precision == 1:
1131
k = 1
1132
X = S(1)
1133
elif initial_precision == 2:
1134
# approximation is 3/2 - 1/2 s
1135
k = 2
1136
X = S(base_ring(3) / base_ring(2)).shift(p) - r
1137
elif initial_precision == 3:
1138
# approximation is (15 - 10 s + 3 s^2) / 8
1139
k = 3
1140
X = (base_ring(1) / base_ring(8)) * (
1141
S(15).shift(2*p) - \
1142
(base_ring(20) * r).shift(p) + \
1143
(base_ring(12) * r.square()) \
1144
)
1145
1146
# The key to the following calculation is that the T^{-m} coefficient
1147
# of every x_i is divisible by p^(ceil(m/p)) (for m >= 0). Therefore if
1148
# we are only expecting an answer correct mod p^k, we can truncate
1149
# beyond the T^{-(k-1)p} term without any problems.
1150
1151
# todo: what would be really nice is to be able to work in a lower
1152
# precision *coefficient ring* when we start the iteration, and move up to
1153
# higher precision rings as the iteration proceeds. This would be feasible
1154
# over Integers(p**n), but quite complicated (maybe impossible) over a more
1155
# general base ring. This might give a decent constant factor speedup;
1156
# or it might not, depending on how much the last iteration dominates the
1157
# whole runtime. My guess is that it isn't worth the effort.
1158
1159
three_halves = base_ring(3) / base_ring(2)
1160
1161
# Newton iteration loop
1162
while k < M:
1163
# target_k = k' = precision we want our answer to be after this iteration
1164
target_k = 2*k
1165
1166
# This prevents us overshooting. For example if the current precision
1167
# is 3 and we want to get to 10, we're better off going up to 5
1168
# instead of 6, because it's less work to get from 5 to 10 than it
1169
# is to get from 6 to 10.
1170
if ceil(log(M/target_k, 2)) == ceil(log(M/(target_k-1), 2)):
1171
target_k -= 1
1172
1173
# temp = T^{p(3k-2)} 1/2 s x_i^3
1174
temp = X.square() * (X * r)
1175
1176
# We know that the final result is only going to be correct mod
1177
# p^(target_k), so we might as well truncate the extraneous terms now.
1178
# temp = T^{p(k'-1)} 1/2 s x_i^3
1179
temp = temp.shift(-p*(3*k - target_k - 1))
1180
1181
# X = T^{p(k'-1)} (3/2 x_i - 1/2 s x_i^3)
1182
# = T^{p(k'-1)} x_{i+1}
1183
X = (three_halves * X).shift(p*(target_k - k)) - temp
1184
1185
k = target_k
1186
1187
# Now k should equal M, since we're up to the correct precision
1188
assert k == M, "Oops, something went wrong in the iteration"
1189
1190
# We should have s^{-1/2} correct to precision M.
1191
# The following line can be uncommented to verify this.
1192
# (It's a slow verification though, can double the whole computation time.)
1193
1194
#assert (p * X.square() * r * base_ring(2)).coeffs() == \
1195
# R(p).shift(p*(2*M - 1)).coeffs()
1196
1197
# Finally incorporate frobenius of dx and x dx, and choose offset that
1198
# compensates for our normalisations by powers of T.
1199
F0 = base_ring(p) * x_to_p_less_one * X
1200
F1 = F0 * x_to_p
1201
offset = ((2*k-1)*p - 1)/2
1202
1203
return F0, F1, offset
1204
1205
1206
1207
1208
def frobenius_expansion_by_series(Q, p, M):
1209
r"""
1210
Computes the action of Frobenius on `dx/y` and on `x dx/y`, using a
1211
series expansion.
1212
1213
(This function computes the same thing as
1214
frobenius_expansion_by_newton(), using a different method.
1215
Theoretically the Newton method should be asymptotically faster,
1216
when the precision gets large. However, in practice, this functions
1217
seems to be marginally faster for moderate precision, so I'm
1218
keeping it here until I figure out exactly why it's faster.)
1219
1220
(This function does *not* yet use the cohomology relations - that
1221
happens afterwards in the "reduction" step.)
1222
1223
More specifically, it finds F0 and F1 in the quotient ring
1224
`R[x, T]/(T - Q(x))`, such that
1225
`F( dx/y) = T^{-r} F0 dx/y`, and
1226
`F(x dx/y) = T^{-r} F1 dx/y` where
1227
`r = ( (2M-3)p - 1 )/2`. (Here `T` is `y^2 = z^{-2}`,
1228
and `R` is the coefficient ring of `Q`.)
1229
1230
`F_0` and `F_1` are computed in the
1231
SpecialCubicQuotientRing associated to `Q`, so all powers
1232
of `x^j` for `j \geq 3` are reduced to powers of
1233
`T`.
1234
1235
It uses the sum
1236
1237
.. math::
1238
1239
F0 = \sum_{k=0}^{M-2} {-1/2 \choose k} p x^{p-1} E^k T^{(M-2-k)p}
1240
1241
and
1242
1243
.. math::
1244
1245
F1 = x^p F0,
1246
1247
where `E = Q(x^p) - Q(x)^p`.
1248
1249
INPUT:
1250
1251
1252
- ``Q`` - cubic polynomial of the form
1253
`Q(x) = x^3 + ax + b`, whose coefficient ring is a
1254
`\ZZ/(p^M)\ZZ` -algebra
1255
1256
- ``p`` - residue characteristic of the `p`-adic field
1257
1258
- ``M`` - `p`-adic precision of the coefficient ring
1259
(this will be used to determine the number of terms in the
1260
series)
1261
1262
OUTPUT:
1263
1264
- ``F0, F1`` - elements of
1265
SpecialCubicQuotientRing(Q), as described above
1266
1267
- ``r`` - non-negative integer, as described above
1268
"""
1269
1270
S = SpecialCubicQuotientRing(Q)
1271
x, _ = S.gens()
1272
base_ring = S.base_ring()
1273
1274
x_to_p_less_1 = x**(p-1)
1275
x_to_p = x_to_p_less_1 * x
1276
1277
# compute frobQ = Q(x^p)
1278
x_to_p_squared = x_to_p * x_to_p
1279
x_to_p_cubed = x_to_p_squared * x_to_p
1280
frobQ = x_to_p_cubed + Q[1]*x_to_p + Q[0]*S(1)
1281
# anticipating the day when p = 3 is supported:
1282
# frobQ = x_to_p_cubed + Q[2]*x_to_p_squared + Q[1]*x_to_p + Q[0]*S(1)
1283
1284
E = frobQ - S(1).shift(p) # E = Q(x^p) - Q(x)^p
1285
1286
offset = int( ((2*M-3)*p-1)/2 )
1287
term = p * x_to_p_less_1
1288
F0 = term.shift((M-2)*p)
1289
1290
# todo: Possible speedup idea, perhaps by a factor of 2, but
1291
# it requires a lot of work:
1292
# Note that p divides E, so p^k divides E^k. So when we are
1293
# working with high powers of E, we're doing a lot more work
1294
# in the multiplications than we need to. To take advantage of
1295
# this we would need some protocol for "lowering the precision"
1296
# of a SpecialCubicQuotientRing. This would be quite messy to
1297
# do properly over an arbitrary base ring. Perhaps it is
1298
# feasible to do for the most common case (i.e. Z/p^nZ).
1299
# (but it probably won't save much time unless p^n is very
1300
# large, because the machine word size is probably pretty
1301
# big anyway.)
1302
1303
for k in range(int(1), int(M-1)):
1304
term = term * E
1305
c = base_ring(binomial(-Integer(1)/2, k))
1306
F0 += (term * c).shift((M-k-2)*p)
1307
1308
return F0, F0 * x_to_p, offset
1309
1310
1311
1312
def adjusted_prec(p, prec):
1313
r"""
1314
Computes how much precision is required in matrix_of_frobenius to
1315
get an answer correct to prec `p`-adic digits.
1316
1317
The issue is that the algorithm used in matrix_of_frobenius
1318
sometimes performs divisions by `p`, so precision is lost
1319
during the algorithm.
1320
1321
The estimate returned by this function is based on Kedlaya's result
1322
(Lemmas 2 and 3 of "Counting Points on Hyperelliptic Curves..."),
1323
which implies that if we start with `M` `p`-adic
1324
digits, the total precision loss is at most
1325
`1 + \lfloor \log_p(2M-3) \rfloor` `p`-adic
1326
digits. (This estimate is somewhat less than the amount you would
1327
expect by naively counting the number of divisions by
1328
`p`.)
1329
1330
INPUT:
1331
1332
1333
- ``p`` - a prime = 5
1334
1335
- ``prec`` - integer, desired output precision, = 1
1336
1337
1338
OUTPUT: adjusted precision (usually slightly more than prec)
1339
"""
1340
1341
# initial estimate:
1342
if prec <= 2:
1343
adjusted = 2
1344
else:
1345
adjusted = prec + int(log(2*prec - 3, p)) - 1
1346
1347
# increase it until we have enough
1348
while adjusted - int(log(2*adjusted - 3, p)) - 1 < prec:
1349
adjusted += 1
1350
1351
return adjusted
1352
1353
1354
1355
def matrix_of_frobenius(Q, p, M, trace=None, compute_exact_forms=False):
1356
"""
1357
Computes the matrix of Frobenius on Monsky-Washnitzer cohomology,
1358
with respect to the basis `(dx/y, x dx/y)`.
1359
1360
INPUT:
1361
1362
1363
- ``Q`` - cubic polynomial `Q(x) = x^3 + ax + b`
1364
defining an elliptic curve `E` by
1365
`y^2 = Q(x)`. The coefficient ring of `Q` should be a
1366
`\ZZ/(p^M)\ZZ`-algebra in which the matrix of
1367
frobenius will be constructed.
1368
1369
- ``p`` - prime = 5 for which E has good reduction
1370
1371
- ``M`` - integer = 2; `p` -adic precision of
1372
the coefficient ring
1373
1374
- ``trace`` - (optional) the trace of the matrix, if
1375
known in advance. This is easy to compute because it's just the
1376
`a_p` of the curve. If the trace is supplied,
1377
matrix_of_frobenius will use it to speed the computation (i.e. we
1378
know the determinant is `p`, so we have two conditions, so
1379
really only column of the matrix needs to be computed. It's
1380
actually a little more complicated than that, but that's the basic
1381
idea.) If trace=None, then both columns will be computed
1382
independently, and you can get a strong indication of correctness
1383
by verifying the trace afterwards.
1384
1385
.. warning::
1386
1387
THE RESULT WILL NOT NECESSARILY BE CORRECT TO M p-ADIC
1388
DIGITS. If you want prec digits of precision, you need to use
1389
the function adjusted_prec(), and then you need to reduce the
1390
answer mod `p^{\mathrm{prec}}` at the end.
1391
1392
1393
OUTPUT:
1394
1395
2x2 matrix of frobenius on Monsky-Washnitzer cohomology,
1396
with entries in the coefficient ring of Q.
1397
1398
EXAMPLES:
1399
1400
A simple example::
1401
1402
sage: p = 5
1403
sage: prec = 3
1404
sage: M = monsky_washnitzer.adjusted_prec(p, prec)
1405
sage: M
1406
5
1407
sage: R.<x> = PolynomialRing(Integers(p**M))
1408
sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M)
1409
sage: A
1410
[3090 187]
1411
[2945 408]
1412
1413
But the result is only accurate to prec digits::
1414
1415
sage: B = A.change_ring(Integers(p**prec))
1416
sage: B
1417
[90 62]
1418
[70 33]
1419
1420
Check trace (123 = -2 mod 125) and determinant::
1421
1422
sage: B.det()
1423
5
1424
sage: B.trace()
1425
123
1426
sage: EllipticCurve([-1, 1/4]).ap(5)
1427
-2
1428
1429
Try using the trace to speed up the calculation::
1430
1431
sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4),
1432
... p, M, -2)
1433
sage: A
1434
[2715 187]
1435
[1445 408]
1436
1437
Hmmm... it looks different, but that's because the trace of our
1438
first answer was only -2 modulo `5^3`, not -2 modulo
1439
`5^5`. So the right answer is::
1440
1441
sage: A.change_ring(Integers(p**prec))
1442
[90 62]
1443
[70 33]
1444
1445
Check it works with only one digit of precision::
1446
1447
sage: p = 5
1448
sage: prec = 1
1449
sage: M = monsky_washnitzer.adjusted_prec(p, prec)
1450
sage: R.<x> = PolynomialRing(Integers(p**M))
1451
sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M)
1452
sage: A.change_ring(Integers(p))
1453
[0 2]
1454
[0 3]
1455
1456
Here's an example that's particularly badly conditioned for using
1457
the trace trick::
1458
1459
sage: p = 11
1460
sage: prec = 3
1461
sage: M = monsky_washnitzer.adjusted_prec(p, prec)
1462
sage: R.<x> = PolynomialRing(Integers(p**M))
1463
sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 + 7*x + 8, p, M)
1464
sage: A.change_ring(Integers(p**prec))
1465
[1144 176]
1466
[ 847 185]
1467
1468
The problem here is that the top-right entry is divisible by 11,
1469
and the bottom-left entry is divisible by `11^2`. So when
1470
you apply the trace trick, neither `F(dx/y)` nor
1471
`F(x dx/y)` is enough to compute the whole matrix to the
1472
desired precision, even if you try increasing the target precision
1473
by one. Nevertheless, ``matrix_of_frobenius`` knows
1474
how to get the right answer by evaluating `F((x+1) dx/y)`
1475
instead::
1476
1477
sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 + 7*x + 8, p, M, -2)
1478
sage: A.change_ring(Integers(p**prec))
1479
[1144 176]
1480
[ 847 185]
1481
1482
The running time is about ``O(p*prec**2)`` (times
1483
some logarithmic factors), so it's feasible to run on fairly large
1484
primes, or precision (or both?!?!)::
1485
1486
sage: p = 10007
1487
sage: prec = 2
1488
sage: M = monsky_washnitzer.adjusted_prec(p, prec)
1489
sage: R.<x> = PolynomialRing(Integers(p**M))
1490
sage: A = monsky_washnitzer.matrix_of_frobenius( # long time
1491
... x^3 - x + R(1/4), p, M) # long time
1492
sage: B = A.change_ring(Integers(p**prec)); B # long time
1493
[74311982 57996908]
1494
[95877067 25828133]
1495
sage: B.det() # long time
1496
10007
1497
sage: B.trace() # long time
1498
66
1499
sage: EllipticCurve([-1, 1/4]).ap(10007) # long time
1500
66
1501
1502
::
1503
1504
sage: p = 5
1505
sage: prec = 300
1506
sage: M = monsky_washnitzer.adjusted_prec(p, prec)
1507
sage: R.<x> = PolynomialRing(Integers(p**M))
1508
sage: A = monsky_washnitzer.matrix_of_frobenius( # long time
1509
... x^3 - x + R(1/4), p, M) # long time
1510
sage: B = A.change_ring(Integers(p**prec)) # long time
1511
sage: B.det() # long time
1512
5
1513
sage: -B.trace() # long time
1514
2
1515
sage: EllipticCurve([-1, 1/4]).ap(5) # long time
1516
-2
1517
1518
Let's check consistency of the results for a range of precisions::
1519
1520
sage: p = 5
1521
sage: max_prec = 60
1522
sage: M = monsky_washnitzer.adjusted_prec(p, max_prec)
1523
sage: R.<x> = PolynomialRing(Integers(p**M))
1524
sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) # long time
1525
sage: A = A.change_ring(Integers(p**max_prec)) # long time
1526
sage: result = [] # long time
1527
sage: for prec in range(1, max_prec): # long time
1528
... M = monsky_washnitzer.adjusted_prec(p, prec) # long time
1529
... R.<x> = PolynomialRing(Integers(p^M),'x') # long time
1530
... B = monsky_washnitzer.matrix_of_frobenius( # long time
1531
... x^3 - x + R(1/4), p, M) # long time
1532
... B = B.change_ring(Integers(p**prec)) # long time
1533
... result.append(B == A.change_ring( # long time
1534
... Integers(p**prec))) # long time
1535
sage: result == [True] * (max_prec - 1) # long time
1536
True
1537
1538
The remaining examples discuss what happens when you take the
1539
coefficient ring to be a power series ring; i.e. in effect you're
1540
looking at a family of curves.
1541
1542
The code does in fact work...
1543
1544
::
1545
1546
sage: p = 11
1547
sage: prec = 3
1548
sage: M = monsky_washnitzer.adjusted_prec(p, prec)
1549
sage: S.<t> = PowerSeriesRing(Integers(p**M), default_prec=4)
1550
sage: a = 7 + t + 3*t^2
1551
sage: b = 8 - 6*t + 17*t^2
1552
sage: R.<x> = PolynomialRing(S)
1553
sage: Q = x**3 + a*x + b
1554
sage: A = monsky_washnitzer.matrix_of_frobenius(Q, p, M) # long time
1555
sage: B = A.change_ring(PowerSeriesRing(Integers(p**prec), 't', default_prec=4)) # long time
1556
sage: B # long time
1557
[1144 + 264*t + 841*t^2 + 1025*t^3 + O(t^4) 176 + 1052*t + 216*t^2 + 523*t^3 + O(t^4)]
1558
[ 847 + 668*t + 81*t^2 + 424*t^3 + O(t^4) 185 + 341*t + 171*t^2 + 642*t^3 + O(t^4)]
1559
1560
The trace trick should work for power series rings too, even in the
1561
badly- conditioned case. Unfortunately I don't know how to compute
1562
the trace in advance, so I'm not sure exactly how this would help.
1563
Also, I suspect the running time will be dominated by the
1564
expansion, so the trace trick won't really speed things up anyway.
1565
Another problem is that the determinant is not always p::
1566
1567
sage: B.det() # long time
1568
11 + 484*t^2 + 451*t^3 + O(t^4)
1569
1570
However, it appears that the determinant always has the property
1571
that if you substitute t - 11t, you do get the constant series p
1572
(mod p\*\*prec). Similarly for the trace. And since the parameter
1573
only really makes sense when it's divisible by p anyway, perhaps
1574
this isn't a problem after all.
1575
"""
1576
1577
M = int(M)
1578
if M < 2:
1579
raise ValueError, "M (=%s) must be at least 2" % M
1580
1581
base_ring = Q.base_ring()
1582
1583
# Expand out frobenius of dx/y and x dx/y.
1584
# (You can substitute frobenius_expansion_by_series here, that will work
1585
# as well. See its docstring for some performance notes.)
1586
F0, F1, offset = frobenius_expansion_by_newton(Q, p, M)
1587
#F0, F1, offset = frobenius_expansion_by_series(Q, p, M)
1588
1589
if compute_exact_forms:
1590
# we need to do all the work to get the exact expressions f such that F(x^i dx/y) = df + \sum a_i x^i dx/y
1591
F0_coeffs = transpose_list(F0.coeffs())
1592
F0_reduced, f_0 = reduce_all(Q, p, F0_coeffs, offset, True)
1593
1594
F1_coeffs = transpose_list(F1.coeffs())
1595
F1_reduced, f_1 = reduce_all(Q, p, F1_coeffs, offset, True)
1596
1597
elif M == 2:
1598
# This implies that only one digit of precision is valid, so we only need
1599
# to reduce the second column. Also, the trace doesn't help at all.
1600
1601
F0_reduced = [ base_ring(0), base_ring(0) ]
1602
1603
F1_coeffs = transpose_list(F1.coeffs())
1604
F1_reduced = reduce_all(Q, p, F1_coeffs, offset)
1605
1606
elif trace is None:
1607
# No trace provided, just reduce F(dx/y) and F(x dx/y) separately.
1608
1609
F0_coeffs = transpose_list(F0.coeffs())
1610
F0_reduced = reduce_all(Q, p, F0_coeffs, offset)
1611
1612
F1_coeffs = transpose_list(F1.coeffs())
1613
F1_reduced = reduce_all(Q, p, F1_coeffs, offset)
1614
1615
else:
1616
# Trace has been provided.
1617
1618
# In most cases this can be used to quickly compute F(dx/y) from
1619
# F(x dx/y). However, if we're unlucky, the (dx/y)-component of
1620
# F(x dx/y) (i.e. the top-right corner of the matrix) may be divisible
1621
# by p, in which case there isn't enough information to get the
1622
# (x dx/y)-component of F(dx/y) to the desired precision. When this
1623
# happens, it turns out that F((x+1) dx/y) always *does* give enough
1624
# information (together with the trace) to get both columns to the
1625
# desired precision.
1626
1627
# First however we need a quick way of telling whether the top-right
1628
# corner is divisible by p, i.e. we want to compute the second column
1629
# of the matrix mod p. We could do this by just running the entire
1630
# algorithm with M = 2 (which assures precision 1). Luckily, we've
1631
# already done most of the work by computing F1 to high precision; so
1632
# all we need to do is extract the coefficients that would correspond
1633
# to the first term of the series, and run the reduction on them.
1634
1635
# todo: actually we only need to do this reduction step mod p^2, not
1636
# mod p^M, which is what the code currently does. If the base ring
1637
# is Integers(p^M), then it's easy. Otherwise it's tricky to construct
1638
# the right ring, I don't know how to do it.
1639
1640
F1_coeffs = transpose_list(F1.coeffs())
1641
F1_modp_coeffs = F1_coeffs[int((M-2)*p):]
1642
# make a copy, because reduce_all will destroy the coefficients:
1643
F1_modp_coeffs = [[cell for cell in row] for row in F1_modp_coeffs]
1644
F1_modp_offset = offset - (M-2)*p
1645
F1_modp_reduced = reduce_all(Q, p, F1_modp_coeffs, F1_modp_offset)
1646
1647
if F1_modp_reduced[0].is_unit():
1648
# If the first entry is invertible mod p, then F(x dx/y) is sufficient
1649
# to get the whole matrix.
1650
1651
F1_reduced = reduce_all(Q, p, F1_coeffs, offset)
1652
1653
F0_reduced = [ base_ring(trace) - F1_reduced[1], None ]
1654
# using that the determinant is p:
1655
F0_reduced[1] = (F0_reduced[0] * F1_reduced[1] - base_ring(p)) \
1656
/ F1_reduced[0]
1657
1658
else:
1659
# If the first entry is zero mod p, then F((x+1) dx/y) will be sufficient
1660
# to get the whole matrix. (Here we are using the fact that the second
1661
# entry *cannot* be zero mod p. This is guaranteed by some results in
1662
# section 3.2 of ``Computation of p-adic Heights and Log Convergence''
1663
# by Mazur, Stein, Tate. But let's quickly check it anyway :-))
1664
assert F1_modp_reduced[1].is_unit(), \
1665
"Hey that's impossible! The second entry in the second column " \
1666
"should be invertible mod p!"
1667
1668
G0_coeffs = transpose_list( (F0 + F1).coeffs())
1669
G0_reduced = reduce_all(Q, p, G0_coeffs, offset)
1670
1671
# Now G0_reduced expresses F((x+1) dx/y) in terms of dx/y and x dx/y.
1672
# Re-express this in terms of (x+1) dx/y and x dx/y.
1673
H0_reduced = [ G0_reduced[0], G0_reduced[1] - G0_reduced[0] ]
1674
1675
# The thing we're about to divide by better be a unit.
1676
assert H0_reduced[1].is_unit(), \
1677
"Hey that's impossible! The second entry in this column " \
1678
"should be invertible mod p!"
1679
1680
# Figure out the second column using the trace...
1681
H1_reduced = [ None, base_ring(trace) - H0_reduced[0] ]
1682
# ... and using that the determinant is p:
1683
H1_reduced[0] = (H0_reduced[0] * H1_reduced[1] - base_ring(p)) \
1684
/ H0_reduced[1]
1685
1686
# Finally, change back to the usual basis (dx/y, x dx/y)
1687
F1_reduced = [ H1_reduced[0], \
1688
H1_reduced[0] + H1_reduced[1] ]
1689
F0_reduced = [ H0_reduced[0] - F1_reduced[0],
1690
H0_reduced[0] + H0_reduced[1] - F1_reduced[1] ]
1691
1692
# One more sanity check: our final result should be congruent mod p
1693
# to the approximation we used earlier.
1694
assert not (
1695
(F1_reduced[0] - F1_modp_reduced[0]).is_unit() or \
1696
(F1_reduced[1] - F1_modp_reduced[1]).is_unit() or \
1697
F0_reduced[0].is_unit() or F0_reduced[1].is_unit()), \
1698
"Hey that's impossible! The output matrix is not congruent mod p " \
1699
"to the approximation found earlier!"
1700
1701
if compute_exact_forms:
1702
return matrix(base_ring, 2, 2, [F0_reduced[0], F1_reduced[0],
1703
F0_reduced[1], F1_reduced[1]]), f_0, f_1
1704
else:
1705
return matrix(base_ring, 2, 2, [F0_reduced[0], F1_reduced[0],
1706
F0_reduced[1], F1_reduced[1]])
1707
1708
1709
1710
1711
#*****************************************************************************
1712
# This is a generalization of the above functionality for hyperelliptic curves.
1713
#
1714
# THIS IS A WORK IN PROGRESS.
1715
#
1716
# I tried to embed must stuff into the rings themselves rather than
1717
# just extract and manipulate lists of coefficients. Hence the implementations
1718
# below are much less optimized, so are much slower, but should hopefully be
1719
# easier to follow. (E.g. one can print/make sense of intermediate results.)
1720
#
1721
# AUTHOR:
1722
# -- Robert Bradshaw (2007-04)
1723
#
1724
#*****************************************************************************
1725
1726
1727
import weakref
1728
1729
from sage.schemes.hyperelliptic_curves.all import is_HyperellipticCurve, HyperellipticCurve
1730
from sage.rings.padics.all import pAdicField
1731
from sage.rings.all import QQ, is_LaurentSeriesRing, is_IntegralDomain
1732
from sage.modules.all import FreeModule, is_FreeModuleElement
1733
1734
from sage.misc.profiler import Profiler
1735
from sage.misc.misc import repr_lincomb
1736
1737
def matrix_of_frobenius_hyperelliptic(Q, p=None, prec=None, M=None):
1738
"""
1739
Computes the matrix of Frobenius on Monsky-Washnitzer cohomology,
1740
with respect to the basis `(dx/2y, x dx/2y, ...x^{d-2} dx/2y)`, where
1741
`d` is the degree of `Q`.
1742
1743
INPUT:
1744
1745
- ``Q`` - monic polynomial `Q(x)`
1746
1747
- ``p`` - prime `\geq 5` for which `E` has good reduction
1748
1749
- ``prec`` - (optional) `p`-adic precision of the coefficient ring
1750
1751
- ``M`` - (optional) adjusted `p`-adic precision of the coefficint ring
1752
1753
OUTPUT:
1754
1755
`(d-1)` x `(d-1)` matrix `M` of Frobenius on Monsky-Washnitzer cohomology,
1756
and list of differentials \{f_i \} such that
1757
1758
.. math::
1759
1760
\phi^* (x^i dx/2y) = df_i + M[i]*vec(dx/2y, ..., x^{d-2} dx/2y)
1761
1762
EXAMPLES::
1763
1764
sage: p = 5
1765
sage: prec = 3
1766
sage: R.<x> = QQ['x']
1767
sage: A,f = monsky_washnitzer.matrix_of_frobenius_hyperelliptic(x^5 - 2*x + 3, p, prec)
1768
sage: A
1769
[ 4*5 + O(5^3) 5 + 2*5^2 + O(5^3) 2 + 3*5 + 2*5^2 + O(5^3) 2 + 5 + 5^2 + O(5^3)]
1770
[ 3*5 + 5^2 + O(5^3) 3*5 + O(5^3) 4*5 + O(5^3) 2 + 5^2 + O(5^3)]
1771
[ 4*5 + 4*5^2 + O(5^3) 3*5 + 2*5^2 + O(5^3) 5 + 3*5^2 + O(5^3) 2*5 + 2*5^2 + O(5^3)]
1772
[ 5^2 + O(5^3) 5 + 4*5^2 + O(5^3) 4*5 + 3*5^2 + O(5^3) 2*5 + O(5^3)]
1773
1774
"""
1775
prof = Profiler()
1776
prof("setup")
1777
if p is None:
1778
try:
1779
K = Q.base_ring()
1780
p = K.prime()
1781
prec = K.precision_cap()
1782
except AttributeError:
1783
raise ValueError, "p and prec must be specified if Q is not defined over a p-adic ring"
1784
if M is None:
1785
M = adjusted_prec(p, prec)
1786
extra_prec_ring = Integers(p**M)
1787
# extra_prec_ring = pAdicField(p, M) # SLOW!
1788
1789
real_prec_ring = pAdicField(p, prec) # pAdicField(p, prec) # To capped absolute?
1790
S = SpecialHyperellipticQuotientRing(Q, extra_prec_ring, True)
1791
MW = S.monsky_washnitzer()
1792
prof("frob basis elements")
1793
F = MW.frob_basis_elements(M, p)
1794
1795
prof("rationalize")
1796
# do reduction over Q in case we have non-integral entries (and it's so much faster than padics)
1797
rational_S = S.change_ring(QQ)
1798
# this is a hack until pAdics are fast
1799
# (They are in the latest development bundle, but its not standard and I'd need to merge.
1800
# (it will periodically cast into this ring to reduce coefficient size)
1801
rational_S._prec_cap = p**M
1802
rational_S._p = p
1803
# S._p = p
1804
# rational_S(F[0]).reduce_fast()
1805
# prof("reduce others")
1806
1807
# rational_S = S.change_ring(pAdicField(p, M))
1808
F = [rational_S(F_i) for F_i in F]
1809
1810
prof("reduce")
1811
reduced = [F_i.reduce_fast(True) for F_i in F]
1812
# reduced = [F_i.reduce() for F_i in F]
1813
1814
#print reduced[0][0].diff() - F[0]
1815
1816
# but the coeffs are WAY more precision than they need to be
1817
# print reduced[0][1]
1818
1819
prof("make matrix")
1820
# now take care of precision capping
1821
M = matrix(real_prec_ring, [a for f, a in reduced])
1822
for i in range(M.ncols()):
1823
for j in range(M.nrows()):
1824
M[i,j] = M[i,j].add_bigoh(prec)
1825
# print prof
1826
return M.transpose(), [f for f, a in reduced]
1827
1828
1829
1830
1831
# For uniqueness (as many of the non-trivial calculations are cached along the way).
1832
1833
_special_ring_cache = {}
1834
_mw_cache = {}
1835
1836
def SpecialHyperellipticQuotientRing(*args):
1837
if _special_ring_cache.has_key(args):
1838
R = _special_ring_cache[args]()
1839
if R is not None:
1840
return R
1841
R = SpecialHyperellipticQuotientRing_class(*args)
1842
_special_ring_cache[args] = weakref.ref(R)
1843
return R
1844
1845
def MonskyWashnitzerDifferentialRing(base_ring):
1846
if _mw_cache.has_key(base_ring):
1847
R = _mw_cache[base_ring]()
1848
if R is not None:
1849
return R
1850
1851
R = MonskyWashnitzerDifferentialRing_class(base_ring)
1852
_mw_cache[base_ring] = weakref.ref(R)
1853
return R
1854
1855
class SpecialHyperellipticQuotientRing_class(CommutativeAlgebra):
1856
1857
_p = None
1858
1859
def __init__(self, Q, R=None, invert_y=True):
1860
if R is None:
1861
R = Q.base_ring()
1862
1863
# Trac ticket #9138: CommutativeAlgebra.__init__ must not be
1864
# done so early. It tries to register a coercion, but that
1865
# requires the hash bein available. But the hash, in its
1866
# default implementation, relies on the string representation,
1867
# which is not available at this point.
1868
#CommutativeAlgebra.__init__(self, R) # moved to below.
1869
1870
x = PolynomialRing(R, 'xx').gen(0)
1871
if is_EllipticCurve(Q):
1872
E = Q
1873
if E.a1() != 0 or E.a2() != 0:
1874
raise NotImplementedError, "Curve must be in Weierstrass normal form."
1875
Q = -E.change_ring(R).defining_polynomial()(x,0,1)
1876
self._curve = E
1877
1878
elif is_HyperellipticCurve(Q):
1879
C = Q
1880
if C.hyperelliptic_polynomials()[1] != 0:
1881
raise NotImplementedError, "Curve must be of form y^2 = Q(x)."
1882
Q = C.hyperelliptic_polynomials()[0].change_ring(R)
1883
self._curve = C
1884
1885
if is_Polynomial(Q):
1886
self._Q = Q.change_ring(R)
1887
self._coeffs = self._Q.coeffs()
1888
if self._coeffs.pop() != 1:
1889
raise NotImplementedError, "Polynomial must be monic."
1890
if not hasattr(self, '_curve'):
1891
if self._Q.degree() == 3:
1892
ainvs = [0, self._Q[2], 0, self._Q[1], self._Q[0]]
1893
self._curve = EllipticCurve(ainvs)
1894
else:
1895
self._curve = HyperellipticCurve(self._Q)
1896
1897
else:
1898
raise NotImplementedError, "Must be an elliptic curve or polynomial Q for y^2 = Q(x)\n(Got element of %s)" % Q.parent()
1899
1900
self._n = degree = int(Q.degree())
1901
self._series_ring = (LaurentSeriesRing if invert_y else PolynomialRing)(R, 'y')
1902
self._series_ring_y = self._series_ring.gen(0)
1903
self._series_ring_0 = self._series_ring(0)
1904
1905
# Trac ticket #9138: Initialise the commutative algebra here!
1906
# Below, we do self(self._poly_ring.gen(0)), which requires
1907
# the initialisation being finished.
1908
CommutativeAlgebra.__init__(self, R)
1909
1910
self._poly_ring = PolynomialRing(self._series_ring, 'x')
1911
1912
self._x = self(self._poly_ring.gen(0))
1913
self._y = self(self._series_ring.gen(0))
1914
1915
self._Q_coeffs = Q.change_ring(self._series_ring).list()
1916
self._dQ = Q.derivative().change_ring(self)(self._x)
1917
self._monsky_washnitzer = MonskyWashnitzerDifferentialRing(self)
1918
1919
self._monomial_diffs = {}
1920
self._monomial_diff_coeffs = {}
1921
1922
def _repr_(self):
1923
y_inverse = ",y^-1" if is_LaurentSeriesRing(self._series_ring) else ""
1924
return "SpecialHyperellipticQuotientRing K[x,y%s] / (y^2 = %s) over %s"%(y_inverse, self._Q, self.base_ring())
1925
1926
def base_extend(self, R):
1927
if R.has_coerce_map_from(self.base_ring()):
1928
return self.change_ring(R)
1929
else:
1930
raise TypeError, "no such base extension"
1931
1932
def change_ring(self, R):
1933
return SpecialHyperellipticQuotientRing(self._Q, R, is_LaurentSeriesRing(self._series_ring))
1934
1935
def __call__(self, val, offset=0, check=True):
1936
if isinstance(val, SpecialHyperellipticQuotientElement) and val.parent() is self:
1937
if offset == 0:
1938
return val
1939
else:
1940
return val << offset
1941
elif isinstance(val, MonskyWashnitzerDifferential):
1942
return self._monsky_washnitzer(val)
1943
return SpecialHyperellipticQuotientElement(self, val, offset, check)
1944
1945
def gens(self):
1946
return self._x, self._y
1947
1948
def x(self):
1949
return self._x
1950
1951
def y(self):
1952
return self._y
1953
1954
def monomial(self, i, j, b=None):
1955
"""
1956
Returns `b y^j x^i`, computed quickly.
1957
"""
1958
i = int(i)
1959
j = int(j)
1960
1961
if 0 < i and i < self._n:
1962
if b is None:
1963
by_to_j = self._series_ring_y << (j-1)
1964
else:
1965
by_to_j = self._series_ring(b) << j
1966
v = [self._series_ring_0] * self._n
1967
v[i] = by_to_j
1968
return self(v)
1969
else:
1970
return (self._x ** i) << j if b is None else self.base_ring()(b) * (self._x ** i) << j
1971
1972
def monomial_diff_coeffs(self, i, j):
1973
r"""
1974
The key here is that the formula for `d(x^iy^j)` is messy
1975
in terms of `i`, but varies nicely with `j`.
1976
1977
.. math::
1978
1979
d(x^iy^j) = y^{j-1} (2ix^{i-1}y^2 + j (A_i(x) + B_i(x)y^2)) \frac{dx}{2y}
1980
1981
1982
Where `A,B` have degree at most `n-1` for each
1983
`i`. Pre-compute `A_i, B_i` for each `i`
1984
the "hard" way, and the rest are easy.
1985
"""
1986
try:
1987
return self._monomial_diff_coeffs[i,j]
1988
except KeyError:
1989
pass
1990
if i < self._n:
1991
try:
1992
A, B, two_i_x_to_i = self._precomputed_diff_coeffs[i]
1993
except AttributeError:
1994
self._precomputed_diff_coeffs = self._precompute_monomial_diffs()
1995
A, B, two_i_x_to_i = self._precomputed_diff_coeffs[i]
1996
if i == 0:
1997
return j*A, j*B
1998
else:
1999
return j*A, j*B + two_i_x_to_i
2000
else:
2001
dg = self.monomial(i, j).diff()
2002
coeffs = [dg.extract_pow_y(j-1), dg.extract_pow_y(j+1)]
2003
self._monomial_diff_coeffs[i,j] = coeffs
2004
return coeffs
2005
2006
def monomial_diff_coeffs_matrices(self):
2007
self.monomial_diff_coeffs(0, 0) # precompute stuff
2008
R = self.base_ring()
2009
mat_1 = matrix(R, self._n, self._n)
2010
mat_2 = matrix(R, self._n, self._n)
2011
for i in range(self._n):
2012
mat_1[i] = self._precomputed_diff_coeffs[i][1]
2013
mat_2[i] = self._precomputed_diff_coeffs[i][2]
2014
return mat_1.transpose(), mat_2.transpose()
2015
2016
def _precompute_monomial_diffs(self):
2017
x, y = self.gens()
2018
R = self.base_ring()
2019
V = FreeModule(R, self.degree())
2020
As = []
2021
for i in range(self.degree()):
2022
dg = self.monomial(i, 1).diff()
2023
two_i_x_to_i = R(2*i) * x**(i-1) * y*y if i > 0 else self(0)
2024
A = dg - self._monsky_washnitzer(two_i_x_to_i)
2025
As.append( (V(A.extract_pow_y(0)), V(A.extract_pow_y(2)), V(two_i_x_to_i.extract_pow_y(2))) )
2026
return As
2027
2028
2029
def Q(self):
2030
return self._Q
2031
2032
def curve(self):
2033
return self._curve
2034
2035
def degree(self):
2036
return self._n
2037
2038
def prime(self):
2039
return self._p
2040
2041
def monsky_washnitzer(self):
2042
return self._monsky_washnitzer
2043
2044
def is_field(self, proof = True):
2045
return False
2046
2047
2048
2049
class SpecialHyperellipticQuotientElement(CommutativeAlgebraElement):
2050
2051
def __init__(self, parent, val=0, offset=0, check=True):
2052
CommutativeAlgebraElement.__init__(self, parent)
2053
if not check:
2054
self._f = parent._poly_ring(val, check=False)
2055
return
2056
if isinstance(val, SpecialHyperellipticQuotientElement):
2057
R = parent.base_ring()
2058
self._f = parent._poly_ring([a.change_ring(R) for a in val._f])
2059
return
2060
if isinstance(val, tuple):
2061
val, offset = val
2062
if isinstance(val, list) and len(val) > 0 and is_FreeModuleElement(val[0]):
2063
val = transpose_list(val)
2064
self._f = parent._poly_ring(val)
2065
if offset != 0:
2066
self._f = self._f.parent()([a << offset for a in self._f], check=False)
2067
2068
def __cmp__(self, other):
2069
"""
2070
EXAMPLES:
2071
"""
2072
return cmp(self._f, other._f)
2073
2074
def change_ring(self, R):
2075
return self.parent().change_ring(R)(self)
2076
2077
def __call__(self, *x):
2078
return self._f(*x)
2079
2080
def __invert__(self):
2081
"""
2082
The general element in our ring is not invertible, but `y` may be. We
2083
do not want to pass to the fraction field.
2084
"""
2085
if self._f.degree() == 0 and self._f[0].is_unit():
2086
return SpecialHyperellipticQuotientElement(self.parent(), ~self._f[0])
2087
else:
2088
raise ZeroDivisionError, "Element not invertible"
2089
2090
def __nonzero__(self):
2091
return not not self._f
2092
2093
def __eq__(self, other):
2094
if not isinstance(other, SpecialHyperellipticQuotientElement):
2095
other = self.parent()(other)
2096
return self._f == other._f
2097
2098
def _add_(self, other):
2099
return SpecialHyperellipticQuotientElement(self.parent(), self._f + other._f)
2100
2101
def _sub_(self, other):
2102
return SpecialHyperellipticQuotientElement(self.parent(), self._f - other._f)
2103
2104
def _mul_(self, other):
2105
# over laurent series, addition and subtraction can be expensive,
2106
# and the degree of this poly is small enough that Karatsuba actually hurts
2107
# significantly in some cases
2108
if self._f[0].valuation() + other._f[0].valuation() > -200:
2109
prod = self._f._mul_generic(other._f)
2110
else:
2111
prod = self._f * other._f
2112
v = prod.list()
2113
parent = self.parent()
2114
Q_coeffs = parent._Q_coeffs
2115
n = len(Q_coeffs) - 1
2116
y2 = self.parent()._series_ring_y << 1
2117
for i in range(len(v)-1, n-1, -1):
2118
for j in range(n):
2119
v[i-n+j] -= Q_coeffs[j] * v[i]
2120
v[i-n] += y2 * v[i]
2121
return SpecialHyperellipticQuotientElement(parent, v[0:n])
2122
2123
def _rmul_(self, c):
2124
coeffs = self._f.list(copy=False)
2125
return self.parent()([c*a for a in coeffs], check=False)
2126
2127
def _lmul_(self, c):
2128
coeffs = self._f.list(copy=False)
2129
return self.parent()([a*c for a in coeffs], check=False)
2130
2131
def __lshift__(self, k):
2132
coeffs = self._f.list(copy=False)
2133
return self.parent()([a << k for a in coeffs], check=False)
2134
2135
def __rshift__(self, k):
2136
coeffs = self._f.list(copy=False)
2137
return self.parent()([a >> k for a in coeffs], check=False)
2138
2139
def truncate_neg(self, n):
2140
coeffs = self._f.list(copy=False)
2141
return self.parent()([a.truncate_neg(n) for a in coeffs], check=False)
2142
2143
def _repr_(self):
2144
x = PolynomialRing(QQ, 'x').gen(0)
2145
coeffs = self._f.list()
2146
return repr_lincomb([(x**i, coeffs[i]) for i in range(len(coeffs))])
2147
2148
def _latex_(self):
2149
x = PolynomialRing(QQ, 'x').gen(0)
2150
coeffs = self._f.list()
2151
return repr_lincomb([(x**i, coeffs[i]) for i in range(len(coeffs))], is_latex=True)
2152
2153
def diff(self):
2154
2155
# try:
2156
# return self._diff_x
2157
# except AttributeError:
2158
# pass
2159
2160
# d(self) = A dx + B dy
2161
# = (2y A + BQ') dx/2y
2162
parent = self.parent()
2163
R = parent.base_ring()
2164
x, y = parent.gens()
2165
v = self._f.list()
2166
n = len(v)
2167
A = parent([R(i) * v[i] for i in range(1,n)])
2168
B = parent([a.derivative() for a in v])
2169
dQ = parent._dQ
2170
return parent._monsky_washnitzer( (R(2) * A << 1) + dQ * B )
2171
# self._diff = self.parent()._monsky_washnitzer( two_y * A + dQ * B )
2172
# return self._diff
2173
2174
def extract_pow_y(self, k):
2175
v = [a[k] for a in self._f.list()]
2176
while len(v) < self.parent()._n:
2177
v.append(0)
2178
return v
2179
2180
def min_pow_y(self):
2181
if self._f.degree() == -1:
2182
return 0
2183
return min([a.valuation() for a in self._f.list()])
2184
2185
def max_pow_y(self):
2186
if self._f.degree() == -1:
2187
return 0
2188
return max([a.degree() for a in self._f.list()])
2189
2190
def coeffs(self, R=None):
2191
"""
2192
Returns the raw coefficients of this element.
2193
2194
INPUT:
2195
2196
- ``R`` - an (optional) base-ring in which to cast the coefficients
2197
2198
OUTPUT:
2199
2200
- ``coeffs`` - a list of coefficients of powers of `x` for each power
2201
of `y`
2202
2203
- ``n`` - an offset indicating the power of `y` of the first list
2204
element
2205
2206
EXAMPLES::
2207
2208
sage: R.<x> = QQ['x']
2209
sage: E = HyperellipticCurve(x^5-3*x+1)
2210
sage: x,y = E.monsky_washnitzer_gens()
2211
sage: x.coeffs()
2212
([(0, 1, 0, 0, 0)], 0)
2213
sage: y.coeffs()
2214
([(0, 0, 0, 0, 0), (1, 0, 0, 0, 0)], 0)
2215
2216
sage: a = sum(n*x^n for n in range(5)); a
2217
x + 2*x^2 + 3*x^3 + 4*x^4
2218
sage: a.coeffs()
2219
([(0, 1, 2, 3, 4)], 0)
2220
sage: a.coeffs(Qp(7))
2221
([(0, 1 + O(7^20), 2 + O(7^20), 3 + O(7^20), 4 + O(7^20))], 0)
2222
sage: (a*y).coeffs()
2223
([(0, 0, 0, 0, 0), (0, 1, 2, 3, 4)], 0)
2224
sage: (a*y^-2).coeffs()
2225
([(0, 1, 2, 3, 4), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0)], -2)
2226
2227
Note that the coefficient list is transposed compared to how they
2228
are stored and printed::
2229
2230
sage: a*y^-2
2231
(y^-2)*x + (2*y^-2)*x^2 + (3*y^-2)*x^3 + (4*y^-2)*x^4
2232
2233
A more complicated example::
2234
2235
sage: a = x^20*y^-3 - x^11*y^2; a
2236
(y^-3-4*y^-1+6*y-4*y^3+y^5)*1 + (-12*y^-3+36*y^-1-36*y-y^2+12*y^3+2*y^4-y^6)*x + (54*y^-3-108*y^-1+54*y+6*y^2-6*y^4)*x^2 + (-108*y^-3+108*y^-1-9*y^2)*x^3 + (81*y^-3)*x^4
2237
sage: raw, offset = a.coeffs()
2238
sage: a.min_pow_y()
2239
-3
2240
sage: offset
2241
-3
2242
sage: raw
2243
[(1, -12, 54, -108, 81),
2244
(0, 0, 0, 0, 0),
2245
(-4, 36, -108, 108, 0),
2246
(0, 0, 0, 0, 0),
2247
(6, -36, 54, 0, 0),
2248
(0, -1, 6, -9, 0),
2249
(-4, 12, 0, 0, 0),
2250
(0, 2, -6, 0, 0),
2251
(1, 0, 0, 0, 0),
2252
(0, -1, 0, 0, 0)]
2253
sage: sum(c * x^i * y^(j+offset) for j, L in enumerate(raw) for i, c in enumerate(L)) == a
2254
True
2255
2256
Can also be used to construct elements::
2257
2258
sage: a.parent()(raw, offset) == a
2259
True
2260
"""
2261
zero = self.base_ring()(0) if R is None else R(0)
2262
y_offset = min(self.min_pow_y(), 0)
2263
y_degree = max(self.max_pow_y(), 0)
2264
coeffs = []
2265
n = y_degree - y_offset + 1
2266
for a in self._f.list():
2267
k = a.valuation() - y_offset
2268
z = a.list()
2269
coeffs.append( [zero] * k + z + [zero]*(n - len(z) - k))
2270
while len(coeffs) < self.parent().degree():
2271
coeffs.append( [zero] * n )
2272
V = FreeModule(self.base_ring() if R is None else R, self.parent().degree())
2273
coeffs = transpose_list(coeffs)
2274
return [V(a) for a in coeffs], y_offset
2275
2276
2277
2278
class MonskyWashnitzerDifferentialRing_class(Module):
2279
2280
def __init__(self, base_ring):
2281
r"""
2282
Class for the ring of Monsky--Washnitzer differentials over a given
2283
base ring.
2284
"""
2285
Module.__init__(self, base_ring)
2286
self._cache = {}
2287
2288
def invariant_differential(self):
2289
"""
2290
Returns `dx/2y` as an element of self.
2291
2292
EXAMPLES::
2293
2294
sage: R.<x> = QQ['x']
2295
sage: C = HyperellipticCurve(x^5-4*x+4)
2296
sage: MW = C.invariant_differential().parent()
2297
sage: MW.invariant_differential()
2298
1 dx/2y
2299
"""
2300
return self(1)
2301
2302
def __call__(self, val, offset=0):
2303
return MonskyWashnitzerDifferential(self, val, offset)
2304
2305
def base_extend(self, R):
2306
"""
2307
Return a new differential ring which is self base-extended to `R`
2308
2309
INPUT:
2310
2311
- ``R`` - ring
2312
2313
OUTPUT:
2314
2315
Self, base-extended to `R`.
2316
2317
EXAMPLES::
2318
2319
sage: R.<x> = QQ['x']
2320
sage: C = HyperellipticCurve(x^5-4*x+4)
2321
sage: MW = C.invariant_differential().parent()
2322
sage: MW.base_ring()
2323
SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 4*x + 4) over Rational Field
2324
sage: MW.base_extend(Qp(5,5)).base_ring()
2325
SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = (1 + O(5^5))*x^5 + (1 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + O(5^5))*x + (4 + O(5^5))) over 5-adic Field with capped relative precision 5
2326
"""
2327
return MonskyWashnitzerDifferentialRing(self.base_ring().base_extend(R))
2328
2329
def change_ring(self, R):
2330
"""
2331
Returns a new differential ring which is self with the coefficient
2332
ring changed to `R`.
2333
2334
INPUT:
2335
2336
- ``R`` - ring of coefficients
2337
2338
OUTPUT:
2339
2340
Self, with the coefficient ring changed to `R`.
2341
2342
EXAMPLES::
2343
2344
sage: R.<x> = QQ['x']
2345
sage: C = HyperellipticCurve(x^5-4*x+4)
2346
sage: MW = C.invariant_differential().parent()
2347
sage: MW.base_ring()
2348
SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 4*x + 4) over Rational Field
2349
sage: MW.change_ring(Qp(5,5)).base_ring()
2350
SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = (1 + O(5^5))*x^5 + (1 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + O(5^5))*x + (4 + O(5^5))) over 5-adic Field with capped relative precision 5
2351
"""
2352
return MonskyWashnitzerDifferentialRing(self.base_ring().change_ring(R))
2353
2354
def degree(self):
2355
"""
2356
Returns the degree of `Q(x)`, where the model of the underlying
2357
hyperelliptic curve of self is given by `y^2 = Q(x)`.
2358
2359
EXAMPLES::
2360
2361
sage: R.<x> = QQ['x']
2362
sage: C = HyperellipticCurve(x^5-4*x+4)
2363
sage: MW = C.invariant_differential().parent()
2364
sage: MW.Q()
2365
x^5 - 4*x + 4
2366
sage: MW.degree()
2367
5
2368
"""
2369
return self.base_ring().degree()
2370
2371
def dimension(self):
2372
"""
2373
Returns the dimension of self.
2374
2375
EXAMPLES::
2376
2377
sage: R.<x> = QQ['x']
2378
sage: C = HyperellipticCurve(x^5-4*x+4)
2379
sage: K = Qp(7,5)
2380
sage: CK = C.change_ring(K)
2381
sage: MW = CK.invariant_differential().parent()
2382
sage: MW.dimension()
2383
4
2384
"""
2385
return self.base_ring().degree()-1
2386
2387
def Q(self):
2388
"""
2389
Returns `Q(x)` where the model of the underlying hyperelliptic curve
2390
of self is given by `y^2 = Q(x)`.
2391
2392
EXAMPLES::
2393
2394
sage: R.<x> = QQ['x']
2395
sage: C = HyperellipticCurve(x^5-4*x+4)
2396
sage: MW = C.invariant_differential().parent()
2397
sage: MW.Q()
2398
x^5 - 4*x + 4
2399
"""
2400
return self.base_ring().Q()
2401
2402
def x_to_p(self, p):
2403
"""
2404
Returns and caches `x^p`, reduced via the relations coming from the
2405
defining polynomial of the hyperelliptic curve.
2406
2407
EXAMPLES::
2408
2409
sage: R.<x> = QQ['x']
2410
sage: C = HyperellipticCurve(x^5-4*x+4)
2411
sage: MW = C.invariant_differential().parent()
2412
sage: MW.x_to_p(3)
2413
x^3
2414
sage: MW.x_to_p(5)
2415
(-4+y^2)*1 + 4*x
2416
sage: MW.x_to_p(101) is MW.x_to_p(101)
2417
True
2418
"""
2419
try:
2420
return self._cache["x_to_p", p]
2421
except KeyError:
2422
x_to_p = self.base_ring().x() ** p
2423
self._cache["x_to_p", p] = x_to_p
2424
return x_to_p
2425
2426
def frob_Q(self, p):
2427
"""
2428
Returns and caches `Q(x^p)`, which is used in computing the image of
2429
`y` under a `p`-power lift of Frobenius to `A^{\dagger}`.
2430
2431
EXAMPLES::
2432
2433
sage: R.<x> = QQ['x']
2434
sage: C = HyperellipticCurve(x^5-4*x+4)
2435
sage: MW = C.invariant_differential().parent()
2436
sage: MW.frob_Q(3)
2437
(-60+48*y^2-12*y^4+y^6)*1 + (192-96*y^2+12*y^4)*x + (-192+48*y^2)*x^2 + 60*x^3
2438
sage: MW.Q()(MW.x_to_p(3))
2439
(-60+48*y^2-12*y^4+y^6)*1 + (192-96*y^2+12*y^4)*x + (-192+48*y^2)*x^2 + 60*x^3
2440
sage: MW.frob_Q(11) is MW.frob_Q(11)
2441
True
2442
"""
2443
try:
2444
return self._cache["frobQ", p]
2445
except KeyError:
2446
x_to_p = self.x_to_p(p)
2447
frobQ = self.base_ring()._Q.change_ring(self.base_ring())(x_to_p)
2448
self._cache["frobQ", p] = frobQ
2449
return frobQ
2450
2451
def frob_invariant_differential(self, prec, p):
2452
r"""
2453
Kedlaya's algorithm allows us to calculate the action of Frobenius on
2454
the Monsky-Washnitzer cohomology. First we lift `\phi` to `A^{\dagger}`
2455
by setting
2456
2457
.. math::
2458
2459
\phi(x) = x^p
2460
2461
\phi(y) = y^p \sqrt{1 + \frac{Q(x^p) - Q(x)^p}{Q(x)^p}}.
2462
2463
Pulling back the differential `dx/2y`, we get
2464
2465
.. math::
2466
2467
\phi^*(dx/2y) = px^{p-1} y(\phi(y))^{-1} dx/2y
2468
= px^{p-1} y^{1-p} \sqrt{1+ \frac{Q(x^p) - Q(x)^p}{Q(x)^p}} dx/2y
2469
2470
Use Newton's method to calculate the square root.
2471
2472
EXAMPLES::
2473
2474
sage: R.<x> = QQ['x']
2475
sage: C = HyperellipticCurve(x^5-4*x+4)
2476
sage: prec = 2
2477
sage: p = 7
2478
sage: MW = C.invariant_differential().parent()
2479
sage: MW.frob_invariant_differential(prec,p)
2480
((67894400*y^-20-81198880*y^-18+40140800*y^-16-10035200*y^-14+1254400*y^-12-62720*y^-10)*1 + (-119503944*y^-20+116064242*y^-18-43753472*y^-16+7426048*y^-14-514304*y^-12+12544*y^-10-1568*y^-8+70*y^-6+7*y^-4)*x + (78905288*y^-20-61014016*y^-18+16859136*y^-16-2207744*y^-14+250880*y^-12-37632*y^-10+3136*y^-8-70*y^-6)*x^2 + (-39452448*y^-20+26148752*y^-18-8085490*y^-16+2007040*y^-14-376320*y^-12+37632*y^-10-1568*y^-8)*x^3 + (21102144*y^-20-18120592*y^-18+8028160*y^-16-2007040*y^-14+250880*y^-12-12544*y^-10)*x^4) dx/2y
2481
"""
2482
prof = Profiler()
2483
prof("setup")
2484
# TODO, would it be useful to be able to take Frobenius of any element? Less efficient?
2485
x, y = self.base_ring().gens()
2486
prof("x_to_p")
2487
x_to_p_less_1 = x**(p-1)
2488
x_to_p = x*x_to_p_less_1
2489
2490
# cache for future use
2491
self._cache["x_to_p", p] = x_to_p
2492
2493
prof("frob_Q")
2494
a = self.frob_Q(p) >> 2*p # frobQ * y^{-2p}
2495
2496
prof("sqrt")
2497
Q = self.base_ring()._Q
2498
2499
# three_halves = Q.parent().base_ring()(Rational((3,2)))
2500
# one_half = Q.parent().base_ring()(Rational((1,2)))
2501
three_halves = self.base_ring()._series_ring.base_ring()(Rational((3,2)))
2502
one_half = self.base_ring()._series_ring.base_ring()(Rational((1,2)))
2503
half_a = a._rmul_(one_half)
2504
2505
# We are solving for t = a^{-1/2} = (F_pQ y^{-p})^{-1/2}
2506
# Newton's method converges because we know the root is in the same residue class as 1.
2507
2508
# t = self.base_ring()(1)
2509
t = self.base_ring()(three_halves) - half_a # first iteration trivial, start with prec 2
2510
2511
for cur_prec in newton_method_sizes(prec)[2:]: # newton_method_sizes = [1, 2, ...]
2512
y_prec = -(2*cur_prec-1)*p+1 # binomial expansion is $\sum p^{k+1} y^{-(2k+1)p+1} f(x)$
2513
# so if we are only correct mod p^prec, can ignore y powers less than y_prec
2514
t_cube = (t*t*t).truncate_neg(y_prec)
2515
t = t._rmul_(three_halves) - (half_a * t_cube).truncate_neg(y_prec)# t = (3/2) t - (1/2) a t^3
2516
# print "a =", a
2517
# print "t =", t
2518
2519
# prof("verify")
2520
# print "a*t^2 =", a * t**2
2521
2522
prof("compose")
2523
F_dx_y = (p * x_to_p_less_1 * t) >> (p-1) # px^{p-1} sqrt(a) * y^{-p+1}
2524
2525
# print "-----", F_dx_y
2526
# print "-----", x_to_p * F_dx_y
2527
prof("done")
2528
# print prof
2529
return MonskyWashnitzerDifferential(self, F_dx_y)
2530
2531
def frob_basis_elements(self, prec, p):
2532
"""
2533
Returns the action of a `p`-power lift of Frobenius on the basis
2534
2535
.. math::
2536
2537
\{ dx/2y, x dx/2y, ..., x^{d-2} dx/2y \}
2538
2539
where `d` is the degree of the underlying hyperelliptic curve.
2540
2541
EXAMPLES::
2542
2543
sage: R.<x> = QQ['x']
2544
sage: C = HyperellipticCurve(x^5-4*x+4)
2545
sage: prec = 1
2546
sage: p = 5
2547
sage: MW = C.invariant_differential().parent()
2548
sage: MW.frob_basis_elements(prec,p)
2549
[((92000*y^-14-74200*y^-12+32000*y^-10-8000*y^-8+1000*y^-6-50*y^-4)*1 + (-194400*y^-14+153600*y^-12-57600*y^-10+9600*y^-8-600*y^-6)*x + (204800*y^-14-153600*y^-12+38400*y^-10-3200*y^-8)*x^2 + (-153600*y^-14+76800*y^-12-9600*y^-10)*x^3 + (63950*y^-14-18550*y^-12+1600*y^-10-400*y^-8+50*y^-6+5*y^-4)*x^4) dx/2y, ((-1391200*y^-14+941400*y^-12-302000*y^-10+76800*y^-8-14400*y^-6+1320*y^-4-30*y^-2)*1 + (2168800*y^-14-1402400*y^-12+537600*y^-10-134400*y^-8+16800*y^-6-720*y^-4)*x + (-1596800*y^-14+1433600*y^-12-537600*y^-10+89600*y^-8-5600*y^-6)*x^2 + (1433600*y^-14-1075200*y^-12+268800*y^-10-22400*y^-8)*x^3 + (-870200*y^-14+445350*y^-12-63350*y^-10+3200*y^-8-600*y^-6+30*y^-4+5*y^-2)*x^4) dx/2y, ((19488000*y^-14-15763200*y^-12+4944400*y^-10-913800*y^-8+156800*y^-6-22560*y^-4+1480*y^-2-10)*1 + (-28163200*y^-14+18669600*y^-12-5774400*y^-10+1433600*y^-8-268800*y^-6+25440*y^-4-760*y^-2)*x + (15062400*y^-14-12940800*y^-12+5734400*y^-10-1433600*y^-8+179200*y^-6-8480*y^-4)*x^2 + (-12121600*y^-14+11468800*y^-12-4300800*y^-10+716800*y^-8-44800*y^-6)*x^3 + (9215200*y^-14-6952400*y^-12+1773950*y^-10-165750*y^-8+5600*y^-6-720*y^-4+10*y^-2+5)*x^4) dx/2y, ((-225395200*y^-14+230640000*y^-12-91733600*y^-10+18347400*y^-8-2293600*y^-6+280960*y^-4-31520*y^-2+1480+10*y^2)*1 + (338048000*y^-14-277132800*y^-12+89928000*y^-10-17816000*y^-8+3225600*y^-6-472320*y^-4+34560*y^-2-720)*x + (-172902400*y^-14+141504000*y^-12-58976000*y^-10+17203200*y^-8-3225600*y^-6+314880*y^-4-11520*y^-2)*x^2 + (108736000*y^-14-109760000*y^-12+51609600*y^-10-12902400*y^-8+1612800*y^-6-78720*y^-4)*x^3 + (-85347200*y^-14+82900000*y^-12-31251400*y^-10+5304150*y^-8-367350*y^-6+8480*y^-4-760*y^-2-10+5*y^2)*x^4) dx/2y]
2550
"""
2551
F_i = self.frob_invariant_differential(prec, p)
2552
x_to_p = self.x_to_p(p)
2553
F = [F_i]
2554
for i in range(1, self.degree()-1):
2555
F_i *= x_to_p
2556
F.append(F_i)
2557
return F
2558
2559
def helper_matrix(self):
2560
"""
2561
We use this to solve for the linear combination of
2562
`x^i y^j` needed to clear all terms with
2563
`y^{j-1}`.
2564
"""
2565
try:
2566
return self._helper_matrix
2567
except:
2568
AttributeError
2569
# The smallest y term of (1/j) d(x^i y^j) is constant for all j.
2570
L = []
2571
x, y = self.base_ring().gens()
2572
n = self.degree()
2573
for i in range(n):
2574
L.append( (y*x**i).diff().extract_pow_y(0) )
2575
A = matrix(L).transpose()
2576
if not is_IntegralDomain(A.base_ring()):
2577
# must be using integer_mod or something to approximate
2578
self._helper_matrix = (~A.change_ring(QQ)).change_ring(A.base_ring())
2579
else:
2580
self._helper_matrix = ~A
2581
return self._helper_matrix
2582
2583
2584
class MonskyWashnitzerDifferential(ModuleElement):
2585
2586
def __init__(self, parent, val=0, offset=0):
2587
r"""
2588
Create an element of the Monsky-Washnitzer ring of differentials, of
2589
the form `F dx/2y`.
2590
2591
INPUT:
2592
2593
- ``parent`` - Monsky-Washnitzer differential ring (instance of class
2594
:class:`~MonskyWashnitzerDifferentialRing_class`
2595
2596
- ``val`` - element of the base ring, or list of coefficients
2597
2598
- ``offset`` - if non-zero, shift val by `y^\text{offset}` (default 0)
2599
2600
EXAMPLES::
2601
2602
sage: R.<x> = QQ['x']
2603
sage: C = HyperellipticCurve(x^5 - 4*x + 4)
2604
sage: x,y = C.monsky_washnitzer_gens()
2605
sage: MW = C.invariant_differential().parent()
2606
sage: sage.schemes.elliptic_curves.monsky_washnitzer.MonskyWashnitzerDifferential(MW, x)
2607
x dx/2y
2608
sage: sage.schemes.elliptic_curves.monsky_washnitzer.MonskyWashnitzerDifferential(MW, y)
2609
y*1 dx/2y
2610
sage: sage.schemes.elliptic_curves.monsky_washnitzer.MonskyWashnitzerDifferential(MW, x, 10)
2611
y^10*x dx/2y
2612
"""
2613
ModuleElement.__init__(self, parent)
2614
if isinstance(val, MonskyWashnitzerDifferential):
2615
val = val._coeff
2616
self._coeff = self.parent().base_ring()(val, offset)
2617
2618
def _add_(left, right):
2619
"""
2620
Returns the sum of left and right, both elements of the
2621
Monsky-Washnitzer ring of differentials.
2622
2623
EXAMPLES::
2624
2625
sage: R.<x> = QQ['x']
2626
sage: C = HyperellipticCurve(x^5-4*x + 4)
2627
sage: x,y = C.monsky_washnitzer_gens()
2628
sage: w = C.invariant_differential()
2629
sage: w + w
2630
2*1 dx/2y
2631
sage: x*w + w
2632
(1 + x) dx/2y
2633
sage: x*w + y*w
2634
(y*1 + x) dx/2y
2635
"""
2636
return MonskyWashnitzerDifferential(left.parent(),
2637
left._coeff + right._coeff)
2638
2639
def _sub_(left, right):
2640
"""
2641
Returns the difference of left and right, both elements of the
2642
Monsky-Washnitzer ring of differentials.
2643
2644
EXAMPLES::
2645
2646
sage: R.<x> = QQ['x']
2647
sage: C = HyperellipticCurve(x^5-4*x+4)
2648
sage: x,y = C.monsky_washnitzer_gens()
2649
sage: w = C.invariant_differential()
2650
sage: w-w
2651
0 dx/2y
2652
sage: x*w-w
2653
((-1)*1 + x) dx/2y
2654
sage: w - x*w - y*w
2655
((1-y)*1 + (-1)*x) dx/2y
2656
"""
2657
return MonskyWashnitzerDifferential(left.parent(),
2658
left._coeff - right._coeff)
2659
2660
def __neg__(self):
2661
"""
2662
Returns the additive inverse of self.
2663
2664
EXAMPLES::
2665
2666
sage: R.<x> = QQ['x']
2667
sage: C = HyperellipticCurve(x^5-4*x+4)
2668
sage: x,y = C.monsky_washnitzer_gens()
2669
sage: w = C.invariant_differential()
2670
sage: -w
2671
((-1)*1) dx/2y
2672
sage: -((y-x)*w)
2673
((-y)*1 + x) dx/2y
2674
"""
2675
return MonskyWashnitzerDifferential(self.parent(), -self._coeff)
2676
2677
def _lmul_(self, a):
2678
"""
2679
Returns `self * a`.
2680
2681
EXAMPLES::
2682
2683
sage: R.<x> = QQ['x']
2684
sage: C = HyperellipticCurve(x^5-4*x+4)
2685
sage: x,y = C.monsky_washnitzer_gens()
2686
sage: w = C.invariant_differential()
2687
sage: w*x
2688
x dx/2y
2689
sage: (w*x)*2
2690
2*x dx/2y
2691
sage: w*y
2692
y*1 dx/2y
2693
sage: w*(x+y)
2694
(y*1 + x) dx/2y
2695
"""
2696
return MonskyWashnitzerDifferential(self.parent(), self._coeff * a)
2697
2698
def _rmul_(self, a):
2699
"""
2700
Returns `a * self`.
2701
2702
EXAMPLES::
2703
2704
sage: R.<x> = QQ['x']
2705
sage: C = HyperellipticCurve(x^5-4*x+4)
2706
sage: x,y = C.monsky_washnitzer_gens()
2707
sage: w = C.invariant_differential()
2708
sage: x*w
2709
x dx/2y
2710
sage: 2*(x*w)
2711
2*x dx/2y
2712
sage: y*w
2713
y*1 dx/2y
2714
sage: (x+y)*w
2715
(y*1 + x) dx/2y
2716
"""
2717
return MonskyWashnitzerDifferential(self.parent(), a * self._coeff)
2718
2719
def coeff(self):
2720
r"""
2721
Returns `A`, where this element is `A dx/2y`.
2722
2723
EXAMPLES::
2724
2725
sage: R.<x> = QQ['x']
2726
sage: C = HyperellipticCurve(x^5-4*x+4)
2727
sage: x,y = C.monsky_washnitzer_gens()
2728
sage: w = C.invariant_differential()
2729
sage: w
2730
1 dx/2y
2731
sage: w.coeff()
2732
1
2733
sage: (x*y*w).coeff()
2734
y*x
2735
"""
2736
return self._coeff
2737
2738
def __nonzero__(self):
2739
"""
2740
EXAMPLES::
2741
2742
sage: R.<x> = QQ['x']
2743
sage: C = HyperellipticCurve(x^5-4*x+4)
2744
sage: x,y = C.monsky_washnitzer_gens()
2745
sage: w = C.invariant_differential()
2746
sage: not w
2747
False
2748
sage: not 0*w
2749
True
2750
sage: not x*y*w
2751
False
2752
"""
2753
return not not self._coeff
2754
2755
def _repr_(self):
2756
"""
2757
EXAMPLES::
2758
2759
sage: R.<x> = QQ['x']
2760
sage: C = HyperellipticCurve(x^5-4*x+4)
2761
sage: x,y = C.monsky_washnitzer_gens()
2762
sage: w = C.invariant_differential()
2763
sage: w
2764
1 dx/2y
2765
sage: (2*x+y)*w
2766
(y*1 + 2*x) dx/2y
2767
"""
2768
s = self._coeff._repr_()
2769
if s.find("+") != -1 or s.find("-") > 0:
2770
s = "(%s)"%s
2771
return s + " dx/2y"
2772
2773
def _latex_(self):
2774
"""
2775
Returns the latex representation of self.
2776
2777
EXAMPLES::
2778
2779
sage: R.<x> = QQ['x']
2780
sage: C = HyperellipticCurve(x^5-4*x+4)
2781
sage: x,y = C.monsky_washnitzer_gens()
2782
sage: w = C.invariant_differential()
2783
sage: latex(w)
2784
1 \frac{dx}{2y}
2785
sage: latex(x*w)
2786
x \frac{dx}{2y}
2787
"""
2788
s = self._coeff._latex_()
2789
if s.find("+") != -1 or s.find("-") > 0:
2790
s = "\\left(%s\\right)"%s
2791
return s + " \\frac{dx}{2y}"
2792
2793
def extract_pow_y(self, k):
2794
"""
2795
Returns the power of `y` in `A` where self is `A dx/2y`.
2796
2797
EXAMPLES::
2798
2799
sage: R.<x> = QQ['x']
2800
sage: C = HyperellipticCurve(x^5-3*x+1)
2801
sage: x,y = C.monsky_washnitzer_gens()
2802
sage: A = y^5 - x*y^3
2803
sage: A.extract_pow_y(5)
2804
[1, 0, 0, 0, 0]
2805
sage: (A * C.invariant_differential()).extract_pow_y(5)
2806
[1, 0, 0, 0, 0]
2807
"""
2808
return self._coeff.extract_pow_y(k)
2809
2810
def min_pow_y(self):
2811
"""
2812
Returns the minimum power of `y` in `A` where self is `A dx/2y`.
2813
2814
EXAMPLES::
2815
2816
sage: R.<x> = QQ['x']
2817
sage: C = HyperellipticCurve(x^5-3*x+1)
2818
sage: x,y = C.monsky_washnitzer_gens()
2819
sage: w = y^5 * C.invariant_differential()
2820
sage: w.min_pow_y()
2821
5
2822
sage: w = (x^2*y^4 + y^5) * C.invariant_differential()
2823
sage: w.min_pow_y()
2824
4
2825
"""
2826
return self._coeff.min_pow_y()
2827
2828
def max_pow_y(self):
2829
"""
2830
Returns the maximum power of `y` in `A` where self is `A dx/2y`.
2831
2832
EXAMPLES::
2833
2834
sage: R.<x> = QQ['x']
2835
sage: C = HyperellipticCurve(x^5-3*x+1)
2836
sage: x,y = C.monsky_washnitzer_gens()
2837
sage: w = y^5 * C.invariant_differential()
2838
sage: w.max_pow_y()
2839
5
2840
sage: w = (x^2*y^4 + y^5) * C.invariant_differential()
2841
sage: w.max_pow_y()
2842
5
2843
"""
2844
return self._coeff.max_pow_y()
2845
2846
def reduce_neg_y(self):
2847
"""
2848
Use homology relations to eliminate negative powers of `y`.
2849
2850
EXAMPLES::
2851
2852
sage: R.<x> = QQ['x']
2853
sage: C = HyperellipticCurve(x^5-3*x+1)
2854
sage: x,y = C.monsky_washnitzer_gens()
2855
sage: (y^-1).diff().reduce_neg_y()
2856
((y^-1)*1, 0 dx/2y)
2857
sage: (y^-5*x^2+y^-1*x).diff().reduce_neg_y()
2858
((y^-1)*x + (y^-5)*x^2, 0 dx/2y)
2859
"""
2860
S = self.parent().base_ring()
2861
R = S.base_ring()
2862
M = self.parent().helper_matrix()
2863
p = S._p
2864
n = S.degree()
2865
x, y = S.gens()
2866
f = S(0)
2867
reduced = self
2868
for j in range(self.min_pow_y()+1, 0):
2869
if p is not None and p.divides(j):
2870
cs = [a/j for a in reduced.extract_pow_y(j-1)]
2871
else:
2872
j_inverse = ~R(j)
2873
cs = [a*j_inverse for a in reduced.extract_pow_y(j-1)]
2874
lin_comb = M * vector(M.base_ring(), cs)
2875
# print "j =", j, "b =", cs, "lin_comb =", lin_comb
2876
g = self.parent().base_ring()(0)
2877
if not lin_comb.is_zero():
2878
for i in range(n):
2879
if lin_comb[i] != 0:
2880
g += S.monomial(i, j, lin_comb[i])
2881
if not g.is_zero():
2882
f += g
2883
reduced -= g.diff()
2884
# print g, g.diff()
2885
# print "reduced", reduced
2886
2887
return f, reduced
2888
2889
def reduce_neg_y_fast(self, even_degree_only=False):
2890
"""
2891
Use homology relations to eliminate negative powers of `y`.
2892
2893
EXAMPLES::
2894
2895
sage: R.<x> = QQ['x']
2896
sage: E = HyperellipticCurve(x^5-3*x+1)
2897
sage: x, y = E.monsky_washnitzer_gens()
2898
sage: (y^-1).diff().reduce_neg_y_fast()
2899
((y^-1)*1, 0 dx/2y)
2900
sage: (y^-5*x^2+y^-1*x).diff().reduce_neg_y_fast()
2901
((y^-1)*x + (y^-5)*x^2, 0 dx/2y)
2902
2903
It leaves non-negative powers of `y` alone::
2904
2905
sage: y.diff()
2906
((-3)*1 + 5*x^4) dx/2y
2907
sage: y.diff().reduce_neg_y_fast()
2908
(0, ((-3)*1 + 5*x^4) dx/2y)
2909
"""
2910
# prof = Profiler()
2911
# prof("reduce setup")
2912
S = self.parent().base_ring()
2913
R = S.base_ring()
2914
M = self.parent().helper_matrix()
2915
2916
# prof("extract coeffs")
2917
coeffs, offset = self.coeffs(R)
2918
V = coeffs[0].parent()
2919
2920
if offset == 0:
2921
return S(0), self
2922
2923
# prof("loop %s"%self.min_pow_y())
2924
forms = []
2925
p = S._p
2926
for j in range(self.min_pow_y()+1, 0):
2927
if (even_degree_only and j % 2 == 0) or coeffs[j-offset-1].is_zero():
2928
forms.append(V(0))
2929
else:
2930
# this is a total hack to deal with the fact that we're using
2931
# rational numbers to approximate fixed precision p-adics
2932
if p is not None and j % 3 == 1:
2933
try:
2934
v = coeffs[j-offset-1]
2935
for kk in range(len(v)):
2936
a = v[kk]
2937
ppow = p**max(-a.valuation(S._p), 0)
2938
v[kk] = ((a * ppow) % S._prec_cap) / ppow
2939
except AttributeError:
2940
pass
2941
lin_comb = ~R(j) * (M * coeffs[j-offset-1])
2942
forms.append(lin_comb)
2943
for i in lin_comb.nonzero_positions():
2944
# g = lin_comb[i] x^i y^j
2945
# self -= dg
2946
coeffs[j-offset+1] -= lin_comb[i] * S.monomial_diff_coeffs(i, j)[1]
2947
2948
# prof("recreate forms")
2949
f = S(forms, offset+1)
2950
reduced = S._monsky_washnitzer(coeffs[-1-offset:], -1)
2951
# print self - f.diff() - reduced
2952
# prof("done")
2953
# print prof
2954
return f, reduced
2955
2956
def reduce_neg_y_faster(self, even_degree_only=False):
2957
"""
2958
Use homology relations to eliminate negative powers of `y`.
2959
"""
2960
# Timings indicate this isn't any faster after all...
2961
2962
S = self.parent().base_ring()
2963
R = S.base_ring()
2964
M = self.parent().helper_matrix()
2965
2966
coeffs, offset = self.coeffs(R)
2967
V = coeffs[0].parent()
2968
zeroV = V(0)
2969
2970
if offset == 0:
2971
return S(0), self
2972
2973
# See monomial_diff_coeffs
2974
# this is the B_i and x_to_i contributions respectively for all i
2975
d_mat_1, d_mat_2 = S.monomial_diff_coeffs_matrices()
2976
2977
forms = []
2978
for j in range(self.min_pow_y()+1, 0):
2979
if coeffs[j-offset-1].is_zero():
2980
forms.append(zeroV)
2981
else:
2982
# this is a total hack to deal with the fact that we're using
2983
# rational numbers to approximate fixed precision p-adics
2984
if j % 3 == 0:
2985
try:
2986
v = coeffs[j-offset-1]
2987
for kk in range(len(v)):
2988
a = v[kk]
2989
ppow = S._p**max(-a.valuation(S._p), 0)
2990
v[kk] = ((a * ppow) % S._prec_cap) / ppow
2991
except AttributeError:
2992
pass
2993
j_inverse = ~R(j)
2994
lin_comb = (M * coeffs[j-offset-1])
2995
forms.append(j_inverse * lin_comb)
2996
coeffs[j-offset+1] -= (d_mat_1 + j_inverse * d_mat_2) * lin_comb
2997
2998
f = S(forms, offset+1)
2999
reduced = S._monsky_washnitzer(coeffs[-1-offset:], -1)
3000
# reduced = self - f.diff()
3001
return f, reduced
3002
3003
3004
def reduce_pos_y(self):
3005
"""
3006
Use homology relations to eliminate positive powers of `y`.
3007
3008
EXAMPLES::
3009
3010
sage: R.<x> = QQ['x']
3011
sage: C = HyperellipticCurve(x^3-4*x+4)
3012
sage: x,y = C.monsky_washnitzer_gens()
3013
sage: (y^2).diff().reduce_pos_y()
3014
(y^2*1, 0 dx/2y)
3015
sage: (y^2*x).diff().reduce_pos_y()
3016
(y^2*x, 0 dx/2y)
3017
sage: (y^92*x).diff().reduce_pos_y()
3018
(y^92*x, 0 dx/2y)
3019
sage: w = (y^3 + x).diff()
3020
sage: w += w.parent()(x)
3021
sage: w.reduce_pos_y_fast()
3022
(y^3*1 + x, x dx/2y)
3023
"""
3024
S = self.parent().base_ring()
3025
series = S.base_ring()
3026
n = S.Q().degree()
3027
p = S._p
3028
x, y = S.gens()
3029
f = S(0)
3030
reduced = self
3031
for j in range(self.max_pow_y(), 0, -1):
3032
for i in range(n-1, -1, -1):
3033
c = reduced.extract_pow_y(j)[i]
3034
# print "x^%s y^%s"%(i,j), c
3035
if c != 0:
3036
g = S.monomial(0, j+1) if i == n-1 else S.monomial(i+1, j-1)
3037
dg = g.diff()
3038
# print reduced, " - ", dg
3039
denom = dg.extract_pow_y(j)[i]
3040
c /= denom
3041
c = g.parent()(c)
3042
f += c * g
3043
reduced -= c * dg
3044
3045
return f, reduced
3046
3047
3048
def reduce_pos_y_fast(self, even_degree_only=False):
3049
"""
3050
Use homology relations to eliminate positive powers of `y`.
3051
3052
EXAMPLES::
3053
3054
sage: R.<x> = QQ['x']
3055
sage: E = HyperellipticCurve(x^3-4*x+4)
3056
sage: x, y = E.monsky_washnitzer_gens()
3057
sage: y.diff().reduce_pos_y_fast()
3058
(y*1, 0 dx/2y)
3059
sage: (y^2).diff().reduce_pos_y_fast()
3060
(y^2*1, 0 dx/2y)
3061
sage: (y^2*x).diff().reduce_pos_y_fast()
3062
(y^2*x, 0 dx/2y)
3063
sage: (y^92*x).diff().reduce_pos_y_fast()
3064
(y^92*x, 0 dx/2y)
3065
sage: w = (y^3 + x).diff()
3066
sage: w += w.parent()(x)
3067
sage: w.reduce_pos_y_fast()
3068
(y^3*1 + x, x dx/2y)
3069
"""
3070
S = self.parent().base_ring()
3071
R = S.base_ring()
3072
n = S.Q().degree()
3073
3074
coeffs, offset = self.coeffs(R)
3075
V = coeffs[0].parent()
3076
zeroV = V(0)
3077
forms = [V(0), V(0)]
3078
3079
for j in range(self.max_pow_y(), -1, -1):
3080
3081
if (even_degree_only and j % 2 == 1) or (j > 0 and coeffs[j-offset].is_zero()):
3082
forms.append(zeroV)
3083
continue
3084
3085
form = V(0)
3086
i = n-1
3087
c = coeffs[j-offset][i]
3088
if c != 0:
3089
dg_coeffs = S.monomial_diff_coeffs(0, j+1)[0]
3090
c /= dg_coeffs[i]
3091
forms[len(forms)-2][0] = c
3092
# self -= c d(y^{j+1})
3093
coeffs[j-offset] -= c*dg_coeffs
3094
3095
if j == 0:
3096
# the others are basis elements
3097
break
3098
3099
for i in range(n-2, -1, -1):
3100
c = coeffs[j-offset][i]
3101
if c != 0:
3102
dg_coeffs = S.monomial_diff_coeffs(i+1, j-1)
3103
denom = dg_coeffs[1][i]
3104
c /= denom
3105
form[i+1] = c
3106
# self -= c d(x^{i+1} y^{j-1})
3107
coeffs[j-offset] -= c*dg_coeffs[1]
3108
coeffs[j-offset-2] -= c*dg_coeffs[0]
3109
forms.append(form)
3110
3111
forms.reverse()
3112
f = S(forms)
3113
reduced = self.parent()(coeffs[:1-offset], offset)
3114
return f, reduced
3115
3116
def reduce(self):
3117
"""
3118
Use homology relations to find `a` and `f` such that this element is
3119
equal to `a + df`, where `a` is given in terms of the `x^i dx/2y`.
3120
3121
EXAMPLES::
3122
3123
sage: R.<x> = QQ['x']
3124
sage: C = HyperellipticCurve(x^5-4*x+4)
3125
sage: x,y = C.monsky_washnitzer_gens()
3126
sage: w = (y*x).diff()
3127
sage: w.reduce()
3128
(y*x, 0 dx/2y)
3129
3130
sage: w = x^4 * C.invariant_differential()
3131
sage: w.reduce()
3132
(1/5*y*1, 4/5*1 dx/2y)
3133
3134
sage: w = sum(QQ.random_element() * x^i * y^j for i in [0..4] for j in [-3..3]) * C.invariant_differential()
3135
sage: f, a = w.reduce()
3136
sage: f.diff() + a - w
3137
0 dx/2y
3138
"""
3139
# print "max_pow_y = ", self.max_pow_y(), "min_pow_y = ", self.min_pow_y()
3140
n = self.parent().base_ring().Q().degree()
3141
f1, a = self.reduce_neg_y()
3142
f2, a = a.reduce_pos_y()
3143
f = f1 + f2
3144
3145
c = a.extract_pow_y(0)[n-1]
3146
if c != 0:
3147
x, y = self.parent().base_ring().gens()
3148
g = y
3149
dg = g.diff()
3150
c = g.parent()(c/dg.extract_pow_y(0)[n-1])
3151
f += c * g
3152
a -= c * dg
3153
# print g, dg
3154
3155
return f, a
3156
3157
def reduce_fast(self, even_degree_only=False):
3158
"""
3159
Use homology relations to find `a` and `f` such that this element is
3160
equal to `a + df`, where `a` is given in terms of the `x^i dx/2y`.
3161
3162
EXAMPLES::
3163
3164
sage: R.<x> = QQ['x']
3165
sage: E = HyperellipticCurve(x^3-4*x+4)
3166
sage: x, y = E.monsky_washnitzer_gens()
3167
sage: x.diff().reduce_fast()
3168
(x, (0, 0))
3169
sage: y.diff().reduce_fast()
3170
(y*1, (0, 0))
3171
sage: (y^-1).diff().reduce_fast()
3172
((y^-1)*1, (0, 0))
3173
sage: (y^-11).diff().reduce_fast()
3174
((y^-11)*1, (0, 0))
3175
sage: (x*y^2).diff().reduce_fast()
3176
(y^2*x, (0, 0))
3177
"""
3178
# print "max_pow_y = ", self.max_pow_y(), "min_pow_y = ", self.min_pow_y()
3179
3180
f1, reduced = self.reduce_neg_y_fast(even_degree_only)
3181
f2, reduced = reduced.reduce_pos_y_fast(even_degree_only)
3182
# f1, reduced = self.reduce_neg_y()
3183
# f2, reduced = reduced.reduce_pos_y()
3184
v = reduced.extract_pow_y(0)
3185
v.pop()
3186
V = FreeModule(self.base_ring().base_ring(), len(v))
3187
return f1+f2, V(v)
3188
3189
def coeffs(self, R=None):
3190
"""
3191
Used to obtain the raw coefficients of a differential, see
3192
:meth:`SpecialHyperellipticQuotientElement.coeffs`
3193
3194
INPUT:
3195
3196
- R - An (optional) base ring in which to cast the coefficients
3197
3198
OUTPUT:
3199
3200
The raw coefficients of $A$ where self is $A dx/2y$.
3201
3202
EXAMPLES::
3203
3204
sage: R.<x> = QQ['x']
3205
sage: C = HyperellipticCurve(x^5-4*x+4)
3206
sage: x,y = C.monsky_washnitzer_gens()
3207
sage: w = C.invariant_differential()
3208
sage: w.coeffs()
3209
([(1, 0, 0, 0, 0)], 0)
3210
sage: (x*w).coeffs()
3211
([(0, 1, 0, 0, 0)], 0)
3212
sage: (y*w).coeffs()
3213
([(0, 0, 0, 0, 0), (1, 0, 0, 0, 0)], 0)
3214
sage: (y^-2*w).coeffs()
3215
([(1, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0)], -2)
3216
"""
3217
return self._coeff.coeffs(R)
3218
3219
def coleman_integral(self, P, Q):
3220
r"""
3221
Computes the definite integral of self from $P$ to $Q$.
3222
3223
INPUT:
3224
3225
- P, Q - Two points on the underlying curve
3226
3227
OUTPUT:
3228
3229
`\int_P^Q \text{self}`
3230
3231
EXAMPLES::
3232
3233
sage: K = pAdicField(5,7)
3234
sage: E = EllipticCurve(K,[-31/3,-2501/108]) #11a
3235
sage: P = E(K(14/3), K(11/2))
3236
sage: w = E.invariant_differential()
3237
sage: w.coleman_integral(P,2*P)
3238
O(5^6)
3239
3240
sage: Q = E.lift_x(3)
3241
sage: w.coleman_integral(P,Q)
3242
2*5 + 4*5^2 + 3*5^3 + 4*5^4 + 3*5^5 + O(5^6)
3243
sage: w.coleman_integral(2*P,Q)
3244
2*5 + 4*5^2 + 3*5^3 + 4*5^4 + 3*5^5 + O(5^6)
3245
sage: (2*w).coleman_integral(P, Q) == 2*(w.coleman_integral(P, Q))
3246
True
3247
"""
3248
3249
return self.parent().base_ring().curve().coleman_integral(self, P, Q)
3250
3251
integrate = coleman_integral
3252
3253
### end of file
3254
3255