Skip to content

Commit

Permalink
sagemathgh-38474: Implement period_lattice for elliptic curves over R…
Browse files Browse the repository at this point in the history
…ealField, ComplexField, etc.

    
Implement `E.period_lattice()` method for elliptic curves over other
fields.

(The code is mostly already there, just need minor adaptation.)

### 📝 Checklist

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion. (not aware of one)
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation
preview.
    
URL: sagemath#38474
Reported by: user202729
Reviewer(s): John Cremona
  • Loading branch information
Release Manager committed Dec 5, 2024
2 parents f71e4ee + 00da70d commit ff8b6ed
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 29 deletions.
49 changes: 49 additions & 0 deletions src/sage/schemes/elliptic_curves/ell_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,55 @@ def isogeny_codomain(self, kernel):
E._fetch_cached_order(self)
return E

def period_lattice(self):
r"""
Return the period lattice of the elliptic curve for the given
embedding of its base field with respect to the differential
`dx/(2y + a_1x + a_3)`.
Only supported for some base rings.
EXAMPLES::
sage: EllipticCurve(RR, [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.00000000000000*x + 6.00000000000000 over Real Field with 53 bits of precision
TESTS::
sage: EllipticCurve(QQ, [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + x + 6 over Rational Field
sage: EllipticCurve(RR, [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.00000000000000*x + 6.00000000000000 over Real Field with 53 bits of precision
sage: EllipticCurve(RealField(100), [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0000000000000000000000000000*x + 6.0000000000000000000000000000 over Real Field with 100 bits of precision
sage: EllipticCurve(CC, [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.00000000000000*x + 6.00000000000000 over Complex Field with 53 bits of precision
sage: EllipticCurve(ComplexField(100), [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0000000000000000000000000000*x + 6.0000000000000000000000000000 over Complex Field with 100 bits of precision
sage: EllipticCurve(AA, [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + x + 6 over Algebraic Real Field
sage: EllipticCurve(QQbar, [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + x + 6 over Algebraic Field
Unsupported cases (the exact error being raised may change in the future)::
sage: EllipticCurve(ZZ, [1, 6]).period_lattice()
Traceback (most recent call last):
...
AttributeError: 'EllipticCurve_generic_with_category' object has no attribute 'period_lattice'
sage: QQt.<t> = QQ[]
sage: EllipticCurve(QQt.fraction_field(), [1, 6]).period_lattice()
Traceback (most recent call last):
...
AttributeError: 'FractionField_1poly_field_with_category' object has no attribute ...
sage: EllipticCurve(GF(7), [1, 6]).period_lattice()
Traceback (most recent call last):
...
IndexError: list index out of range
"""
from sage.schemes.elliptic_curves.period_lattice import PeriodLattice_ell
return PeriodLattice_ell(self)

def kernel_polynomial_from_point(self, P, *, algorithm=None):
r"""
Given a point `P` on this curve which generates a rational subgroup,
Expand Down
137 changes: 108 additions & 29 deletions src/sage/schemes/elliptic_curves/period_lattice.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,12 @@
from sage.misc.cachefunc import cached_method
from sage.misc.lazy_import import lazy_import
from sage.modules.free_module import FreeModule_generic_pid
from sage.rings.complex_mpfr import ComplexField, ComplexNumber
from sage.rings.complex_mpfr import ComplexField, ComplexNumber, ComplexField_class
from sage.rings.infinity import Infinity
from sage.rings.integer_ring import ZZ
from sage.rings.qqbar import AA, QQbar
from sage.rings.rational_field import QQ
from sage.rings.real_mpfr import RealField, RealNumber
from sage.rings.real_mpfr import RealField, RealField_class, RealNumber
from sage.schemes.elliptic_curves.constructor import EllipticCurve
from sage.structure.richcmp import richcmp_method, richcmp, richcmp_not_equal

Expand Down Expand Up @@ -212,9 +213,16 @@ def __init__(self, E, embedding=None):
sage: L = PeriodLattice_ell(E,emb)
sage: L == loads(dumps(L))
True
"""
from sage.rings.qqbar import AA, QQbar
Elliptic curve over imaginary number field without ``embedding`` specified::
sage: E = EllipticCurve(QQ[I], [5, -3*I])
sage: L = PeriodLattice_ell(E, embedding=None)
sage: L.elliptic_logarithm(E(I+1, I+2))
-0.773376784700140 - 0.177736018028666*I
sage: L.elliptic_exponential(_)
(1.00000000000000 - 1.00000000000000*I : 2.00000000000000 - 1.00000000000000*I : 1.00000000000000)
"""
# First we cache the elliptic curve with this period lattice:

self.E = E
Expand All @@ -223,12 +231,20 @@ def __init__(self, E, embedding=None):
# the given embedding:

K = E.base_field()
self.is_approximate = isinstance(K, (RealField_class, ComplexField_class))
if embedding is None:
embs = K.embeddings(AA)
real = len(embs) > 0
if not real:
embs = K.embeddings(QQbar)
embedding = embs[0]
if K in (AA, QQbar):
embedding = K.hom(QQbar)
real = K == AA
elif self.is_approximate:
embedding = K.hom(K)
real = isinstance(K, RealField_class)
else:
embs = K.embeddings(AA)
real = len(embs) > 0
if not real:
embs = K.embeddings(QQbar)
embedding = embs[0]
else:
embedding = refine_embedding(embedding, Infinity)
real = embedding(K.gen()).imag().is_zero()
Expand All @@ -255,20 +271,24 @@ def __init__(self, E, embedding=None):
# The ei are used both for period computation and elliptic
# logarithms.

self.Ebar = self.E.change_ring(self.embedding)
self.f2 = self.Ebar.two_division_polynomial()
if self.is_approximate:
self.f2 = self.E.two_division_polynomial()
else:
self.Ebar = self.E.change_ring(self.embedding)
self.f2 = self.Ebar.two_division_polynomial()
if self.real_flag == 1: # positive discriminant
self._ei = self.f2.roots(AA,multiplicities=False)
self._ei = self.f2.roots(K if self.is_approximate else AA,multiplicities=False)
self._ei.sort() # e1 < e2 < e3
e1, e2, e3 = self._ei
elif self.real_flag == -1: # negative discriminant
self._ei = self.f2.roots(QQbar, multiplicities=False)
self._ei = self.f2.roots(ComplexField(K.precision()) if self.is_approximate else QQbar, multiplicities=False)
self._ei = sorted(self._ei, key=lambda z: z.imag())
e1, e3, e2 = self._ei # so e3 is real
e3 = AA(e3)
if not self.is_approximate:
e3 = AA(e3)
self._ei = [e1, e2, e3]
else:
self._ei = self.f2.roots(QQbar, multiplicities=False)
self._ei = self.f2.roots(ComplexField(K.precision()) if self.is_approximate else QQbar, multiplicities=False)
e1, e2, e3 = self._ei

# The quantities sqrt(e_i-e_j) are cached (as elements of
Expand Down Expand Up @@ -329,7 +349,8 @@ def __repr__(self):
To: Algebraic Real Field
Defn: a |--> 1.259921049894873?
"""
if self.E.base_field() is QQ:
K = self.E.base_field()
if K in (QQ, AA, QQbar) or isinstance(K, (RealField_class, ComplexField_class)):
return "Period lattice associated to %s" % (self.E)
return "Period lattice associated to %s with respect to the embedding %s" % (self.E, self.embedding)

Expand Down Expand Up @@ -630,6 +651,13 @@ def tau(self, prec=None, algorithm='sage'):
w1, w2 = self.normalised_basis(prec=prec, algorithm=algorithm)
return w1/w2

@cached_method
def _compute_default_prec(self):
r"""
Internal function to compute the default precision to be used if nothing is passed in.
"""
return self.E.base_field().precision() if self.is_approximate else RealField().precision()

@cached_method
def _compute_periods_real(self, prec=None, algorithm='sage'):
r"""
Expand Down Expand Up @@ -670,13 +698,13 @@ def _compute_periods_real(self, prec=None, algorithm='sage'):
1.9072648860892725468182549468 - 1.3404778596244020196600112394*I)
"""
if prec is None:
prec = 53
prec = self._compute_default_prec()
R = RealField(prec)
C = ComplexField(prec)

if algorithm == 'pari':
ainvs = self.E.a_invariants()
if self.E.base_field() is not QQ:
if self.E.base_field() is not QQ and not self.is_approximate:
ainvs = [C(self.embedding(ai)).real() for ai in ainvs]

# The precision for omega() is determined by ellinit()
Expand All @@ -688,9 +716,8 @@ def _compute_periods_real(self, prec=None, algorithm='sage'):
raise ValueError("invalid value of 'algorithm' parameter")

pi = R.pi()
# Up to now everything has been exact in AA or QQbar, but now
# we must go transcendental. Only now is the desired
# precision used!
# Up to now everything has been exact in AA or QQbar (unless self.is_approximate),
# but now we must go transcendental. Only now is the desired precision used!
if self.real_flag == 1: # positive discriminant
a, b, c = (R(x) for x in self._abc)
w1 = R(pi/a.agm(b)) # least real period
Expand Down Expand Up @@ -758,12 +785,11 @@ def _compute_periods_complex(self, prec=None, normalise=True):
0.692321964451917
"""
if prec is None:
prec = RealField().precision()
prec = self._compute_default_prec()
C = ComplexField(prec)

# Up to now everything has been exact in AA, but now we
# must go transcendental. Only now is the desired
# precision used!
# Up to now everything has been exact in AA or QQbar (unless self.is_approximate),
# but now we must go transcendental. Only now is the desired precision used!
pi = C.pi()
a, b, c = (C(x) for x in self._abc)
if (a+b).abs() < (a-b).abs():
Expand Down Expand Up @@ -1107,7 +1133,7 @@ def sigma(self, z, prec=None, flag=0):
2.60912163570108 - 0.200865080824587*I
"""
if prec is None:
prec = RealField().precision()
prec = self._compute_default_prec()
try:
return self.E.pari_curve().ellsigma(z, flag, precision=prec)
except AttributeError:
Expand Down Expand Up @@ -1421,7 +1447,7 @@ def e_log_RC(self, xP, yP, prec=None, reduce=True):
2.06711431204080 - 1.73451485683471*I
"""
if prec is None:
prec = RealField().precision()
prec = self._compute_default_prec()
# Note: using log2(prec) + 3 guard bits is usually enough.
# To avoid computing a logarithm, we use 40 guard bits which
# should be largely enough in practice.
Expand Down Expand Up @@ -1709,11 +1735,61 @@ def elliptic_logarithm(self, P, prec=None, reduce=True):
1.17058357737548897849026170185581196033579563441850967539191867385734983296504066660506637438866628981886518901958717288150400849746892393771983141354 - 1.13513899565966043682474529757126359416758251309237866586896869548539516543734207347695898664875799307727928332953834601460994992792519799260968053875*I
sage: L.elliptic_logarithm(P, prec=1000)
1.17058357737548897849026170185581196033579563441850967539191867385734983296504066660506637438866628981886518901958717288150400849746892393771983141354014895386251320571643977497740116710952913769943240797618468987304985625823413440999754037939123032233879499904283600304184828809773650066658885672885 - 1.13513899565966043682474529757126359416758251309237866586896869548539516543734207347695898664875799307727928332953834601460994992792519799260968053875387282656993476491590607092182964878750169490985439873220720963653658829712494879003124071110818175013453207439440032582917366703476398880865439217473*I
Elliptic curve over ``QQbar``::
sage: E = EllipticCurve(QQbar, [sqrt(2), I])
sage: L = E.period_lattice()
sage: P = E.lift_x(3)
sage: L.elliptic_logarithm(P)
-1.97657221097437 - 1.05021415535949*I
sage: L.elliptic_exponential(_)
(3.00000000000000 + 9.20856947066460e-16*I : -5.59022723358798 - 0.0894418024719718*I : 1.00000000000000)
sage: L.elliptic_logarithm(P, prec=100)
-3.4730631218714889933426781799 + 0.44627675553762761312098773197*I
sage: L.elliptic_exponential(_)
(3.0000000000000000000000000000 - 1.4773628579202938936348512161e-30*I : -5.5902272335879800026836302686 - 0.089441802471969391005702381090*I : 1.0000000000000000000000000000)
Real approximate field, negative discriminant. Note that the output precision uses the precision of the base field::
sage: E = EllipticCurve(RealField(100), [1, 6])
sage: L = E.period_lattice()
sage: L.real_flag
-1
sage: P = E(3, 6)
sage: L.elliptic_logarithm(P)
2.4593388737550379526023682666
sage: L.elliptic_exponential(_)
(3.0000000000000000000000000000 : 5.9999999999999999999999999999 : 1.0000000000000000000000000000)
Real approximate field, positive discriminant::
sage: E = EllipticCurve(RealField(100), [-4, 3])
sage: L = E.period_lattice()
sage: L.real_flag
1
sage: P = E.lift_x(4)
sage: L.elliptic_logarithm(P)
0.51188849089267627141925354967
sage: L.elliptic_exponential(_)
(4.0000000000000000000000000000 : -7.1414284285428499979993998114 : 1.0000000000000000000000000000)
Complex approximate field::
sage: E = EllipticCurve(ComplexField(100), [I, 3*I+4])
sage: L = E.period_lattice()
sage: L.real_flag
0
sage: P = E.lift_x(4)
sage: L.elliptic_logarithm(P)
-1.1447032790074574712147458157 - 0.72429843602171875396186134806*I
sage: L.elliptic_exponential(_)
(4.0000000000000000000000000000 + 1.2025589033682610849950210280e-30*I : -8.2570982991257407680322611854 - 0.42387771989714340809597881586*I : 1.0000000000000000000000000000)
"""
if P.curve() is not self.E:
raise ValueError("Point is on the wrong curve")
if prec is None:
prec = RealField().precision()
prec = self._compute_default_prec()
if P.is_zero():
return ComplexField(prec)(0)

Expand Down Expand Up @@ -1926,7 +2002,10 @@ def elliptic_exponential(self, z, to_curve=True):

if to_curve:
K = x.parent()
v = refine_embedding(self.embedding, Infinity)
if self.is_approximate:
v = self.embedding
else:
v = refine_embedding(self.embedding, Infinity)
a1, a2, a3, a4, a6 = (K(v(a)) for a in self.E.ainvs())
b2 = K(v(self.E.b2()))
x = x - b2 / 12
Expand Down

0 comments on commit ff8b6ed

Please sign in to comment.