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

Refactor period lattice #39212

Merged
merged 8 commits into from
Feb 21, 2025
18 changes: 18 additions & 0 deletions src/sage/matrix/matrix_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,24 @@ def characteristic(self):
"""
return self.base_ring().characteristic()

def is_exact(self):
"""
Test whether elements of this matrix space are represented exactly.

OUTPUT:

Return ``True`` if elements of this matrix space are represented exactly, i.e.,
there is no precision loss when doing arithmetic.

EXAMPLES::

sage: MatrixSpace(ZZ, 3).is_exact()
True
sage: MatrixSpace(RR, 3).is_exact()
False
"""
return self._base.is_exact()

def _has_default_implementation(self):
r"""
EXAMPLES::
Expand Down
18 changes: 18 additions & 0 deletions src/sage/modules/free_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,24 @@ def is_sparse(self):
"""
return self.__is_sparse

def is_exact(self):
"""
Test whether elements of this module are represented exactly.

OUTPUT:

Return ``True`` if elements of this module are represented exactly, i.e.,
there is no precision loss when doing arithmetic.

EXAMPLES::

sage: (ZZ^2).is_exact()
True
sage: (RR^2).is_exact()
False
"""
return self._base.is_exact()

def _an_element_(self):
"""
Return an arbitrary element of a free module.
Expand Down
23 changes: 0 additions & 23 deletions src/sage/rings/ring.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -518,29 +518,6 @@ cdef class Ring(ParentWithGens):
else:
return False

cpdef bint is_exact(self) except -2:
"""
Return ``True`` if elements of this ring are represented exactly, i.e.,
there is no precision loss when doing arithmetic.

.. NOTE::

This defaults to ``True``, so even if it does return ``True`` you
have no guarantee (unless the ring has properly overloaded this).

EXAMPLES::

sage: QQ.is_exact() # indirect doctest
True
sage: ZZ.is_exact()
True
sage: Qp(7).is_exact() # needs sage.rings.padics
False
sage: Zp(7, type='capped-abs').is_exact() # needs sage.rings.padics
False
"""
return True

def order(self):
"""
The number of elements of ``self``.
Expand Down
4 changes: 4 additions & 0 deletions src/sage/schemes/elliptic_curves/ell_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -1548,10 +1548,14 @@ def period_lattice(self):
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(RDF, [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0*x + 6.0 over Real Double Field
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(CDF, [1, 6]).period_lattice()
Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0*x + 6.0 over Complex Double Field
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()
Expand Down
13 changes: 13 additions & 0 deletions src/sage/schemes/elliptic_curves/ell_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,19 @@ def is_on_curve(self, x, y):
a = self.ainvs()
return y**2 + a[0]*x*y + a[2]*y == x**3 + a[1]*x**2 + a[3]*x + a[4]

def is_exact(self):
"""
Test whether elements of this elliptic curve are represented exactly.

EXAMPLES::

sage: EllipticCurve(QQ, [1, 2]).is_exact()
True
sage: EllipticCurve(RR, [1, 2]).is_exact()
False
"""
return self.__base_ring.is_exact()

def a_invariants(self):
r"""
The `a`-invariants of this elliptic curve, as a tuple.
Expand Down
103 changes: 77 additions & 26 deletions src/sage/schemes/elliptic_curves/period_lattice.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@

import sage.rings.abc

from sage.categories.morphism import IdentityMorphism
from sage.misc.cachefunc import cached_method
from sage.misc.lazy_import import lazy_import
from sage.modules.free_module import FreeModule_generic_pid
Expand All @@ -116,7 +117,7 @@
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, RealField_class, RealNumber
from sage.rings.real_mpfr import RealField, RealNumber
from sage.schemes.elliptic_curves.constructor import EllipticCurve
from sage.structure.richcmp import richcmp_method, richcmp, richcmp_not_equal

Expand Down Expand Up @@ -231,14 +232,14 @@ def __init__(self, E, embedding=None):
# the given embedding:

K = E.base_field()
self.is_approximate = isinstance(K, (RealField_class, ComplexField_class))
self._is_exact = K.is_exact()
if embedding is None:
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)
elif not self._is_exact:
embedding = IdentityMorphism(K)
real = isinstance(K, (sage.rings.abc.RealField, sage.rings.abc.RealDoubleField))
else:
embs = K.embeddings(AA)
real = len(embs) > 0
Expand Down Expand Up @@ -271,24 +272,24 @@ def __init__(self, E, embedding=None):
# The ei are used both for period computation and elliptic
# logarithms.

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

# The quantities sqrt(e_i-e_j) are cached (as elements of
Expand Down Expand Up @@ -350,7 +351,7 @@ def __repr__(self):
Defn: a |--> 1.259921049894873?
"""
K = self.E.base_field()
if K in (QQ, AA, QQbar) or isinstance(K, (RealField_class, ComplexField_class)):
if K in (QQ, AA, QQbar) or isinstance(self.embedding, IdentityMorphism):
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 @@ -656,7 +657,7 @@ 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()
return RealField().precision() if self._is_exact else self.E.base_field().precision()

@cached_method
def _compute_periods_real(self, prec=None, algorithm='sage'):
Expand Down Expand Up @@ -704,7 +705,7 @@ def _compute_periods_real(self, prec=None, algorithm='sage'):

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

# The precision for omega() is determined by ellinit()
Expand All @@ -716,7 +717,7 @@ 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 (unless self.is_approximate),
# Up to now everything has been exact in AA or QQbar (if self._is_exact),
# 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)
Expand Down Expand Up @@ -788,7 +789,7 @@ def _compute_periods_complex(self, prec=None, normalise=True):
prec = self._compute_default_prec()
C = ComplexField(prec)

# Up to now everything has been exact in AA or QQbar (unless self.is_approximate),
# Up to now everything has been exact in AA or QQbar (if self._is_exact),
# 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)
Expand Down Expand Up @@ -1166,6 +1167,38 @@ def curve(self):
"""
return self.E

@property
def is_approximate(self):
"""
``self.is_approximate`` is deprecated, use ``not self.curve().is_exact()`` instead.

TESTS::

sage: E = EllipticCurve(ComplexField(100), [I, 3*I+4])
sage: L = E.period_lattice()
sage: L.is_approximate
doctest:...: DeprecationWarning: The attribute is_approximate for period lattice is deprecated,
use self.curve().is_exact() instead.
See https://github.com/sagemath/sage/issues/39212 for details.
True
sage: L.curve() is E
True
sage: E.is_exact()
False
sage: E = EllipticCurve(QQ, [0, 2])
sage: L = E.period_lattice()
sage: L.is_approximate
False
sage: L.curve() is E
True
sage: E.is_exact()
True
"""
from sage.misc.superseded import deprecation
deprecation(39212, "The attribute is_approximate for period lattice is "
"deprecated, use self.curve().is_exact() instead.")
return not self._is_exact

def ei(self):
r"""
Return the x-coordinates of the 2-division points of the elliptic curve associated
Expand Down Expand Up @@ -1757,10 +1790,19 @@ def elliptic_logarithm(self, P, prec=None, reduce=True):
sage: L.real_flag
-1
sage: P = E(3, 6)
sage: L.elliptic_logarithm(P)
sage: L.elliptic_logarithm(P) # abs tol 1e-26
2.4593388737550379526023682666
sage: L.elliptic_exponential(_)
sage: L.elliptic_exponential(_) # abs tol 1e-26
(3.0000000000000000000000000000 : 5.9999999999999999999999999999 : 1.0000000000000000000000000000)
sage: E = EllipticCurve(RDF, [1, 6])
sage: L = E.period_lattice()
sage: L.real_flag
-1
sage: P = E(3, 6)
sage: L.elliptic_logarithm(P) # abs tol 1e-13
2.45933887375504
sage: L.elliptic_exponential(_) # abs tol 1e-13
(3.00000000000000 : 6.00000000000001 : 1.00000000000000)

Real approximate field, positive discriminant::

Expand All @@ -1769,9 +1811,9 @@ def elliptic_logarithm(self, P, prec=None, reduce=True):
sage: L.real_flag
1
sage: P = E.lift_x(4)
sage: L.elliptic_logarithm(P)
sage: L.elliptic_logarithm(P) # abs tol 1e-26
0.51188849089267627141925354967
sage: L.elliptic_exponential(_)
sage: L.elliptic_exponential(_) # abs tol 1e-26
(4.0000000000000000000000000000 : -7.1414284285428499979993998114 : 1.0000000000000000000000000000)

Complex approximate field::
Expand All @@ -1781,10 +1823,19 @@ def elliptic_logarithm(self, P, prec=None, reduce=True):
sage: L.real_flag
0
sage: P = E.lift_x(4)
sage: L.elliptic_logarithm(P)
sage: L.elliptic_logarithm(P) # abs tol 1e-26
-1.1447032790074574712147458157 - 0.72429843602171875396186134806*I
sage: L.elliptic_exponential(_)
sage: L.elliptic_exponential(_) # abs tol 1e-26
(4.0000000000000000000000000000 + 1.2025589033682610849950210280e-30*I : -8.2570982991257407680322611854 - 0.42387771989714340809597881586*I : 1.0000000000000000000000000000)
sage: E = EllipticCurve(CDF, [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) # abs tol 1e-13
-1.14470327900746 - 0.724298436021719*I
sage: L.elliptic_exponential(_) # abs tol 1e-13
(4.00000000000000 - 0*I : -8.25709829912574 - 0.423877719897148*I : 1.00000000000000)
"""
if P.curve() is not self.E:
raise ValueError("Point is on the wrong curve")
Expand Down Expand Up @@ -2002,10 +2053,10 @@ def elliptic_exponential(self, z, to_curve=True):

if to_curve:
K = x.parent()
if self.is_approximate:
v = self.embedding
else:
if self._is_exact:
v = refine_embedding(self.embedding, Infinity)
else:
v = self.embedding
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
6 changes: 3 additions & 3 deletions src/sage/structure/parent.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2863,17 +2863,17 @@ cdef class Parent(sage.structure.category_object.CategoryObject):

cpdef bint is_exact(self) except -2:
"""
Test whether the ring is exact.
Test whether elements of this parent are represented exactly.

.. NOTE::

This defaults to true, so even if it does return ``True``
you have no guarantee (unless the ring has properly
you have no guarantee (unless the parent has properly
overloaded this).

OUTPUT:

Return ``True`` if elements of this ring are represented exactly, i.e.,
Return ``True`` if elements of this parent are represented exactly, i.e.,
there is no precision loss when doing arithmetic.

EXAMPLES::
Expand Down
Loading