Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement period_lattice for elliptic curves over RealField, ComplexField, etc. #38474

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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::

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()

Check failure on line 1569 in src/sage/schemes/elliptic_curves/ell_field.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Traceback (most recent call last): File "sage/structure/category_object.pyx", line 857, in sage.structure.category_object.CategoryObject.getattr_from_category return self._cached_methods[name] KeyError: 'embeddings' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 715, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1136, in compile_and_execute exec(compiled, globs) File "<doctest sage.schemes.elliptic_curves.ell_field.EllipticCurve_field.period_lattice[10]>", line 1, in <module> EllipticCurve(QQt.fraction_field(), [Integer(1), Integer(6)]).period_lattice() File "/sage/src/sage/schemes/elliptic_curves/ell_field.py", line 1579, in period_lattice return PeriodLattice_ell(self) File "/sage/src/sage/schemes/elliptic_curves/period_lattice.py", line 243, in __init__ embs = K.embeddings(AA) File "sage/structure/category_object.pyx", line 851, in sage.structure.category_object.CategoryObject.__getattr__ return self.getattr_from_category(name) File "sage/structure/category_object.pyx", line 866, in sage.structure.category_object.CategoryObject.getattr_from_category attr = getattr_from_other_class(self, cls, name) File "sage/cpython/getattr.pyx", line 358, in sage.cpython.getattr.getattr_from_other_class raise AttributeError(dummy_error_message) AttributeError: 'FractionField_1poly_field_with_category' object has no attribute 'embeddings'. Did you mean: 'an_embedding'?
Traceback (most recent call last):
...
AttributeError: 'FractionField_1poly_field_with_category' object has no attribute 'embeddings'
sage: EllipticCurve(GF(7), [1, 6]).period_lattice()

Check failure on line 1573 in src/sage/schemes/elliptic_curves/ell_field.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 715, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1136, in compile_and_execute exec(compiled, globs) File "<doctest sage.schemes.elliptic_curves.ell_field.EllipticCurve_field.period_lattice[11]>", line 1, in <module> EllipticCurve(GF(Integer(7)), [Integer(1), Integer(6)]).period_lattice() File "/sage/src/sage/schemes/elliptic_curves/ell_field.py", line 1579, in period_lattice return PeriodLattice_ell(self) File "/sage/src/sage/schemes/elliptic_curves/period_lattice.py", line 247, in __init__ embedding = embs[0] IndexError: list index out of range
Traceback (most recent call last):
...
AttributeError: 'FiniteField_prime_modn_with_category' object has no attribute 'embeddings'
"""
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
Loading