diff --git a/src/sage/libs/eclib/__init__.pxd b/src/sage/libs/eclib/__init__.pxd index 3f99f99..d44d4fb 100644 --- a/src/sage/libs/eclib/__init__.pxd +++ b/src/sage/libs/eclib/__init__.pxd @@ -12,9 +12,11 @@ from libcpp.pair cimport pair from sage.libs.ntl.types cimport ZZ_c -# NOTE: eclib includes have specific dependencies and must be included -# in a specific order. So we start by listing all relevant include files -# in the correct order. +# NOTE: eclib used to have specific dependencies, so that they had to +# be included in a specific order. Although this is no longer the +# case, we start by listing all relevant include files in the correct +# order. + cdef extern from "eclib/vector.h": pass cdef extern from "eclib/xmod.h": pass cdef extern from "eclib/svector.h": pass diff --git a/src/sage/libs/eclib/interface.py b/src/sage/libs/eclib/interface.py index e898456..493b5f1 100644 --- a/src/sage/libs/eclib/interface.py +++ b/src/sage/libs/eclib/interface.py @@ -21,17 +21,16 @@ Check that ``eclib`` is imported as needed:: sage: [k for k in sys.modules if k.startswith("sage.libs.eclib")] [] sage: EllipticCurve('11a1').mwrank_curve() - y^2+ y = x^3 - x^2 - 10*x - 20 + y^2 + y = x^3 - x^2 - 10 x - 20 sage: [k for k in sys.modules if k.startswith("sage.libs.eclib")] ['...'] """ - +import sys from sage.structure.sage_object import SageObject from sage.rings.all import Integer from sage.rings.integer_ring import IntegerRing -from .mwrank import _Curvedata, _two_descent, _mw - +from .mwrank import _Curvedata, _two_descent, _mw, parse_point_list class mwrank_EllipticCurve(SageObject): r""" @@ -67,7 +66,7 @@ class mwrank_EllipticCurve(SageObject): sage: e = mwrank_EllipticCurve([3, -4]) sage: e - y^2 = x^3 + 3*x - 4 + y^2 = x^3 + 3 x - 4 sage: e.ainvs() [0, 0, 0, 3, -4] @@ -127,6 +126,7 @@ class mwrank_EllipticCurve(SageObject): # place holders self.__saturate = -2 # not yet saturated + self.__descent = None def __reduce__(self): r""" @@ -137,12 +137,9 @@ class mwrank_EllipticCurve(SageObject): sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: E.__reduce__() (, ([0, 0, 1, -7, 6], False)) - - """ return mwrank_EllipticCurve, (self.__ainvs, self.__verbose) - def set_verbose(self, verbose): """ Set the verbosity of printing of output by the :meth:`two_descent()` and @@ -247,53 +244,27 @@ class mwrank_EllipticCurve(SageObject): sage: E = mwrank_EllipticCurve([0,-1,1,0,0]) sage: E.__repr__() - 'y^2+ y = x^3 - x^2 ' + 'y^2 + y = x^3 - x^2' """ - # TODO: Is the use (or omission) of spaces here intentional? - a = self.ainvs() - s = "y^2" - if a[0] == -1: - s += "- x*y " - elif a[0] == 1: - s += "+ x*y " - elif a[0] != 0: - s += "+ %s*x*y "%a[0] - if a[2] == -1: - s += " - y" - elif a[2] == 1: - s += "+ y" - elif a[2] != 0: - s += "+ %s*y"%a[2] - s += " = x^3 " - if a[1] == -1: - s += "- x^2 " - elif a[1] == 1: - s += "+ x^2 " - elif a[1] != 0: - s += "+ %s*x^2 "%a[1] - if a[3] == -1: - s += "- x " - elif a[3] == 1: - s += "+ x " - elif a[3] != 0: - s += "+ %s*x "%a[3] - if a[4] == -1: - s += "-1" - elif a[4] == 1: - s += "+1" - elif a[4] != 0: - s += "+ %s"%a[4] - s = s.replace("+ -","- ") - return s - + a1, a2, a3, a4, a6 = self.__ainvs + # we do not assume a1, a2, a3 are reduced to {0,1}, {-1,0,1}, {0,1} + coeff = lambda a: ''.join([" +" if a > 0 else " -", + " " + str(abs(a)) if abs(a) > 1 else ""]) + return ''.join(['y^2', + ' '.join([coeff(a1), 'xy']) if a1 else '', + ' '.join([coeff(a3), 'y']) if a3 else '', + ' = x^3', + ' '.join([coeff(a2), 'x^2']) if a2 else '', + ' '.join([coeff(a4), 'x']) if a4 else '', + ' '.join([" +" if a6 > 0 else " -", str(abs(a6))]) if a6 else '']) def two_descent(self, - verbose = True, - selmer_only = False, - first_limit = 20, - second_limit = 8, - n_aux = -1, - second_descent = True): + verbose=True, + selmer_only=False, + first_limit=20, + second_limit=8, + n_aux=-1, + second_descent=True): r""" Compute 2-descent data for this curve. @@ -374,16 +345,14 @@ class mwrank_EllipticCurve(SageObject): second_limit = int(second_limit) n_aux = int(n_aux) second_descent = int(second_descent) # convert from bool to (int) 0 or 1 - # TODO: Don't allow limits above some value...??? - # (since otherwise mwrank just sets limit tiny) self.__descent = _two_descent() self.__descent.do_descent(self.__curve, - verbose, - selmer_only, - first_limit, - second_limit, - n_aux, - second_descent) + verbose, + selmer_only, + first_limit, + second_limit, + n_aux, + second_descent) if not self.__descent.ok(): raise RuntimeError("A 2-descent did not complete successfully.") self.__saturate = -2 # not yet saturated @@ -398,11 +367,9 @@ class mwrank_EllipticCurve(SageObject): sage: E._mwrank_EllipticCurve__two_descent_data() """ - try: - return self.__descent - except AttributeError: + if self.__descent is None: self.two_descent(self.__verbose) - return self.__descent + return self.__descent def conductor(self): """ @@ -565,22 +532,24 @@ class mwrank_EllipticCurve(SageObject): R = self.__two_descent_data().regulator() return float(R) - def saturate(self, bound=-1): + def saturate(self, bound=-1, lower=2): """ - Compute the saturation of the Mordell-Weil group at all - primes up to ``bound``. + Compute the saturation of the Mordell-Weil group. INPUT: - - ``bound`` (int, default -1) -- Use `-1` (the default) to - saturate at *all* primes, `0` for no saturation, or `n` (a - positive integer) to saturate at all primes up to `n`. + - ``bound`` (int, default -1) -- If `-1`, saturate at *all* + primes by computing a bound on the saturation index, + otherwise saturate at all primes up to the minimum of + ``bound`` and the saturation index bound. + + - ``lower`` (int, default 2) -- Only saturate at primes not + less than this. EXAMPLES: Since the 2-descent automatically saturates at primes up to - 20, it is not easy to come up with an example where saturation - has any effect:: + 20, further saturation often has no effect:: sage: E = mwrank_EllipticCurve([0, 0, 0, -1002231243161, 0]) sage: E.gens() @@ -599,7 +568,7 @@ class mwrank_EllipticCurve(SageObject): """ bound = int(bound) if self.__saturate < bound: - self.__two_descent_data().saturate(bound) + self.__two_descent_data().saturate(bound, lower) self.__saturate = bound def gens(self): @@ -613,8 +582,7 @@ class mwrank_EllipticCurve(SageObject): [[0, -1, 1]] """ self.saturate() - L = eval(self.__two_descent_data().getbasis().replace(":",",")) - return [[Integer(x), Integer(y), Integer(z)] for (x,y,z) in L] + return parse_point_list(self.__two_descent_data().getbasis()) def certain(self): r""" @@ -760,65 +728,37 @@ class mwrank_MordellWeil(SageObject): sage: EQ.search(1) P1 = [0:1:0] is torsion point, order 1 P1 = [-3:0:1] is generator number 1 - saturating up to 20...Checking 2-saturation - Points have successfully been 2-saturated (max q used = 7) - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 7) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 23) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 41) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 17) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 43) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 31) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 37) + saturating up to 20...Saturation index bound (for points of good reduction) = 3 + Reducing saturation bound from given value 20 to computed index bound 3 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 7) + Checking 3-saturation + Points were proved 3-saturated (max q used = 7) done P2 = [-2:3:1] is generator number 2 - saturating up to 20...Checking 2-saturation + saturating up to 20...Saturation index bound (for points of good reduction) = 4 + Reducing saturation bound from given value 20 to computed index bound 4 + Checking saturation at [ 2 3 ] + Checking 2-saturation possible kernel vector = [1,1] This point may be in 2E(Q): [14:-52:1] - ...and it is! + ...and it is! Replacing old generator #1 with new generator [1:-1:1] + Reducing index bound from 4 to 2 Points have successfully been 2-saturated (max q used = 7) Index gain = 2^1 - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 13) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 67) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 53) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 73) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 103) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 113) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 47) - done (index = 2). + done, index = 2. Gained index 2, new generators = [ [1:-1:1] [-2:3:1] ] P3 = [-14:25:8] is generator number 3 - saturating up to 20...Checking 2-saturation - Points have successfully been 2-saturated (max q used = 11) - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 13) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 71) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 101) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 127) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 151) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 139) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 179) - done (index = 1). + saturating up to 20...Saturation index bound (for points of good reduction) = 3 + Reducing saturation bound from given value 20 to computed index bound 3 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 11) + Checking 3-saturation + Points were proved 3-saturated (max q used = 13) + done, index = 1. P4 = [-1:3:1] = -1*P1 + -1*P2 + -1*P3 (mod torsion) P4 = [0:2:1] = 2*P1 + 0*P2 + 1*P3 (mod torsion) P4 = [2:13:8] = -3*P1 + 1*P2 + -1*P3 (mod torsion) @@ -878,7 +818,7 @@ class mwrank_MordellWeil(SageObject): sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: EQ = mwrank_MordellWeil(E) sage: EQ.__reduce__() - (, (y^2+ y = x^3 - 7*x + 6, True, 1, 999)) + (, (y^2 + y = x^3 - 7 x + 6, True, 1, 999)) """ return mwrank_MordellWeil, (self.__curve, self.__verbose, self.__pp, self.__maxr) @@ -902,12 +842,10 @@ class mwrank_MordellWeil(SageObject): """ return "Subgroup of Mordell-Weil group: %s"%self.__mw - def process(self, v, sat=0): - """ - This function allows one to add points to a :class:`mwrank_MordellWeil` object. + def process(self, v, saturation_bound=0): + """Process points in the list ``v``. - Process points in the list ``v``, with saturation at primes up to - ``sat``. If ``sat`` is zero (the default), do no saturation. + This function allows one to add points to a :class:`mwrank_MordellWeil` object. INPUT: @@ -915,8 +853,9 @@ class mwrank_MordellWeil(SageObject): list of triples of integers, which define points on the curve. - - ``sat`` (int, default 0) -- saturate at primes up to ``sat``, or at - *all* primes if ``sat`` is zero. + - ``saturation_bound`` (int, default 0) -- saturate at primes up to + ``saturation_bound``, or at *all* primes if ``saturation_bound`` is -1; when ``saturation_bound`` + is 0 (the default), do no saturation.. OUTPUT: @@ -939,11 +878,11 @@ class mwrank_MordellWeil(SageObject): sage: EQ.points() [[1, -1, 1], [-2, 3, 1], [-14, 25, 8]] - Example to illustrate the saturation parameter ``sat``:: + Example to illustrate the saturation parameter ``saturation_bound``:: sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: EQ = mwrank_MordellWeil(E) - sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], sat=20) + sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], saturation_bound=20) P1 = [1547:-2967:343] is generator number 1 ... Gained index 5, new generators = [ [-2:3:1] [-14:25:8] [1:-1:1] ] @@ -956,7 +895,7 @@ class mwrank_MordellWeil(SageObject): sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: EQ = mwrank_MordellWeil(E) - sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], sat=0) + sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], saturation_bound=0) P1 = [1547:-2967:343] is generator number 1 P2 = [2707496766203306:864581029138191:2969715140223272] is generator number 2 P3 = [-13422227300:-49322830557:12167000000] is generator number 3 @@ -965,55 +904,92 @@ class mwrank_MordellWeil(SageObject): sage: EQ.regulator() 375.42920288254555 sage: EQ.saturate(2) # points were not 2-saturated - saturating basis...Saturation index bound = 93 - WARNING: saturation at primes p > 2 will not be done; - ... + saturating basis...Saturation index bound (for points of good reduction) = 93 + Only p-saturating for p up to given value 2. + The resulting points may not be p-saturated for p between this and the computed index bound 93 + Checking saturation at [ 2 ] + Checking 2-saturation + possible kernel vector = [1,0,0] + This point may be in 2E(Q): [1547:-2967:343] + ...and it is! + Replacing old generator #1 with new generator [-2:3:1] + Reducing index bound from 93 to 46 + Points have successfully been 2-saturated (max q used = 11) + Index gain = 2^1 + done Gained index 2 - New regulator = 93.857... - (False, 2, '[ ]') + New regulator = 93.85730072 + (True, 2, '[ ]') sage: EQ.points() [[-2, 3, 1], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]] sage: EQ.regulator() 93.85730072063639 sage: EQ.saturate(3) # points were not 3-saturated - saturating basis...Saturation index bound = 46 - WARNING: saturation at primes p > 3 will not be done; - ... + saturating basis...Saturation index bound (for points of good reduction) = 46 + Only p-saturating for p up to given value 3. + The resulting points may not be p-saturated for p between this and the computed index bound 46 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 11) + Checking 3-saturation + possible kernel vector = [0,1,0] + This point may be in 3E(Q): [2707496766203306:864581029138191:2969715140223272] + ...and it is! + Replacing old generator #2 with new generator [-14:25:8] + Reducing index bound from 46 to 15 + Points have successfully been 3-saturated (max q used = 13) + Index gain = 3^1 + done Gained index 3 - New regulator = 10.428... - (False, 3, '[ ]') + New regulator = 10.42858897 + (True, 3, '[ ]') sage: EQ.points() [[-2, 3, 1], [-14, 25, 8], [-13422227300, -49322830557, 12167000000]] sage: EQ.regulator() 10.4285889689596 sage: EQ.saturate(5) # points were not 5-saturated - saturating basis...Saturation index bound = 15 - WARNING: saturation at primes p > 5 will not be done; - ... + saturating basis...Saturation index bound (for points of good reduction) = 15 + Only p-saturating for p up to given value 5. + The resulting points may not be p-saturated for p between this and the computed index bound 15 + Checking saturation at [ 2 3 5 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 11) + Checking 3-saturation + Points were proved 3-saturated (max q used = 13) + Checking 5-saturation + possible kernel vector = [0,0,1] + This point may be in 5E(Q): [-13422227300:-49322830557:12167000000] + ...and it is! + Replacing old generator #3 with new generator [1:-1:1] + Reducing index bound from 15 to 3 + Points have successfully been 5-saturated (max q used = 71) + Index gain = 5^1 + done Gained index 5 - New regulator = 0.417... - (False, 5, '[ ]') + New regulator = 0.4171435588 + (True, 5, '[ ]') sage: EQ.points() [[-2, 3, 1], [-14, 25, 8], [1, -1, 1]] sage: EQ.regulator() 0.417143558758384 sage: EQ.saturate() # points are now saturated - saturating basis...Saturation index bound = 3 + saturating basis...Saturation index bound (for points of good reduction) = 3 + Tamagawa index primes are [ ] Checking saturation at [ 2 3 ] - Checking 2-saturation + Checking 2-saturation Points were proved 2-saturated (max q used = 11) - Checking 3-saturation + Checking 3-saturation Points were proved 3-saturated (max q used = 13) done (True, 1, '[ ]') """ if not isinstance(v, list): raise TypeError("v (=%s) must be a list"%v) - sat = int(sat) + saturation_bound = int(saturation_bound) for P in v: - if not isinstance(P, (list,tuple)) or len(P) != 3: + if not isinstance(P, (list, tuple)) or len(P) != 3: raise TypeError("v (=%s) must be a list of 3-tuples (or 3-element lists) of ints"%v) - self.__mw.process(P, sat) + self.__mw.process(P, saturation_bound) def regulator(self): """ @@ -1091,23 +1067,21 @@ class mwrank_MordellWeil(SageObject): """ return self.__mw.rank() - def saturate(self, max_prime=-1, odd_primes_only=False): - r""" - Saturate this subgroup of the Mordell-Weil group. + def saturate(self, max_prime=-1, min_prime=2): + r"""Saturate this subgroup of the Mordell-Weil group. INPUT: - - ``max_prime`` (int, default -1) -- saturation is performed for - all primes up to ``max_prime``. If `-1` (the default), an + - ``max_prime`` (int, default -1) -- If `-1` (the default), an upper bound is computed for the primes at which the subgroup - may not be saturated, and this is used; however, if the - computed bound is greater than a value set by the ``eclib`` - library (currently 97) then no saturation will be attempted - at primes above this. + may not be saturated, and saturation is performed for all + primes up to this bound. Otherwise, the bound used is the + minimum of ``max_prime`` and the computed bound. - - ``odd_primes_only`` (bool, default ``False``) -- only do - saturation at odd primes. (If the points have been found - via :meth:`two_descent` they should already be 2-saturated.) + - ``min_prime`` (int, default 2) -- only do saturation at + primes no less than this. (For example, if the points have + been found via :meth:`two_descent` they should already be + 2-saturated so a value of 3 is appropriate.) OUTPUT: @@ -1115,40 +1089,35 @@ class mwrank_MordellWeil(SageObject): - ``ok`` (bool) -- ``True`` if and only if the saturation was provably successful at all primes attempted. If the default - was used for ``max_prime`` and no warning was output about - the computed saturation bound being too high, then ``True`` - indicates that the subgroup is saturated at *all* - primes. + was used for ``max_prime``, then ``True`` indicates that the + subgroup is saturated at *all* primes. - ``index`` (int) -- the index of the group generated by the original points in their saturation. - ``unsatlist`` (list of ints) -- list of primes at which - saturation could not be proved or achieved. Increasing the - precision should correct this, since it happens when - a linear combination of the points appears to be a multiple - of `p` but cannot be divided by `p`. (Note that ``eclib`` - uses floating point methods based on elliptic logarithms to - divide points.) + saturation could not be proved or achieved. .. note:: - We emphasize that if this function returns ``True`` as the - first return argument (``ok``), and if the default was used for the - parameter ``max_prime``, then the points in the basis after - calling this function are saturated at *all* primes, - i.e., saturating at the primes up to ``max_prime`` are - sufficient to saturate at all primes. Note that the - function might not have needed to saturate at all primes up - to ``max_prime``. It has worked out what prime you need to - saturate up to, and that prime might be smaller than ``max_prime``. + In versions up to v20190909, ``eclib`` used floating point + methods based on elliptic logarithms to divide points, and + did not compute the precision necessary, which could casue + failures. Since v20210310, ``eclib`` uses exact method based + on division polynomials, which should mean that such + failures does not happen. .. note:: - Currently (May 2010), this does not remember the result of - calling :meth:`search()`. So calling :meth:`search()` up - to height 20 then calling :meth:`saturate()` results in - another search up to height 18. + We emphasize that if this function returns ``True`` as the + first return argument (``ok``), and if the default was used + for the parameter ``max_prime``, then the points in the + basis after calling this function are saturated at *all* + primes, i.e., saturating at the primes up to ``max_prime`` + are sufficient to saturate at all primes. Note that the + function computes an upper bound for the index of + saturation, and does no work for primes greater than this + even if ``max_prime`` is larger. EXAMPLES:: @@ -1160,7 +1129,7 @@ class mwrank_MordellWeil(SageObject): automatic saturation at this stage we set the parameter ``sat`` to 0 (which is in fact the default):: - sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], sat=0) + sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], saturation_bound=0) P1 = [1547:-2967:343] is generator number 1 P2 = [2707496766203306:864581029138191:2969715140223272] is generator number 2 P3 = [-13422227300:-49322830557:12167000000] is generator number 3 @@ -1172,12 +1141,12 @@ class mwrank_MordellWeil(SageObject): Now we saturate at `p=2`, and gain index 2:: sage: EQ.saturate(2) # points were not 2-saturated - saturating basis...Saturation index bound = 93 - WARNING: saturation at primes p > 2 will not be done; + saturating basis...Saturation index bound (for points of good reduction) = 93 + Only p-saturating for p up to given value 2. ... Gained index 2 New regulator = 93.857... - (False, 2, '[ ]') + (True, 2, '[ ]') sage: EQ Subgroup of Mordell-Weil group: [[-2:3:1], [2707496766203306:864581029138191:2969715140223272], [-13422227300:-49322830557:12167000000]] sage: EQ.regulator() @@ -1186,12 +1155,12 @@ class mwrank_MordellWeil(SageObject): Now we saturate at `p=3`, and gain index 3:: sage: EQ.saturate(3) # points were not 3-saturated - saturating basis...Saturation index bound = 46 - WARNING: saturation at primes p > 3 will not be done; + saturating basis...Saturation index bound (for points of good reduction) = 46 + Only p-saturating for p up to given value 3. ... Gained index 3 New regulator = 10.428... - (False, 3, '[ ]') + (True, 3, '[ ]') sage: EQ Subgroup of Mordell-Weil group: [[-2:3:1], [-14:25:8], [-13422227300:-49322830557:12167000000]] sage: EQ.regulator() @@ -1200,12 +1169,12 @@ class mwrank_MordellWeil(SageObject): Now we saturate at `p=5`, and gain index 5:: sage: EQ.saturate(5) # points were not 5-saturated - saturating basis...Saturation index bound = 15 - WARNING: saturation at primes p > 5 will not be done; + saturating basis...Saturation index bound (for points of good reduction) = 15 + Only p-saturating for p up to given value 5. ... Gained index 5 New regulator = 0.417... - (False, 5, '[ ]') + (True, 5, '[ ]') sage: EQ Subgroup of Mordell-Weil group: [[-2:3:1], [-14:25:8], [1:-1:1]] sage: EQ.regulator() @@ -1215,7 +1184,8 @@ class mwrank_MordellWeil(SageObject): the points are now provably saturated at all primes:: sage: EQ.saturate() # points are now saturated - saturating basis...Saturation index bound = 3 + saturating basis...Saturation index bound (for points of good reduction) = 3 + Tamagawa index primes are [ ] Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -1229,7 +1199,7 @@ class mwrank_MordellWeil(SageObject): sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: EQ = mwrank_MordellWeil(E) - sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], sat=5) + sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], saturation_bound=5) P1 = [1547:-2967:343] is generator number 1 ... Gained index 5, new generators = [ [-2:3:1] [-14:25:8] [1:-1:1] ] @@ -1242,7 +1212,8 @@ class mwrank_MordellWeil(SageObject): verify that full saturation has been done:: sage: EQ.saturate() - saturating basis...Saturation index bound = 3 + saturating basis...Saturation index bound (for points of good reduction) = 3 + Tamagawa index primes are [ ] Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -1255,8 +1226,9 @@ class mwrank_MordellWeil(SageObject): index of the points in their saturation is at most 3, then proves saturation at 2 and at 3, by reducing the points modulo all primes of good reduction up to 11, respectively 13. + """ - ok, index, unsat = self.__mw.saturate(int(max_prime), odd_primes_only) + ok, index, unsat = self.__mw.saturate(int(max_prime), int(min_prime)) return bool(ok), int(str(index)), unsat def search(self, height_limit=18, verbose=False): @@ -1271,9 +1243,9 @@ class mwrank_MordellWeil(SageObject): .. note:: - On 32-bit machines, this *must* be < 21.48 else + On 32-bit machines, this *must* be < 21.48 (`31\log(2)`) else `\exp(h_{\text{lim}}) > 2^{31}` and overflows. On 64-bit machines, it - must be *at most* 43.668. However, this bound is a logarithmic + must be *at most* 43.668 (`63\log(2)`) . However, this bound is a logarithmic bound and increasing it by just 1 increases the running time by (roughly) `\exp(1.5)=4.5`, so searching up to even 20 takes a very long time. @@ -1320,8 +1292,10 @@ class mwrank_MordellWeil(SageObject): Subgroup of Mordell-Weil group: [[4413270:10381877:27000]] """ height_limit = float(height_limit) - if height_limit >= 21.4: # TODO: docstring says 21.48 (for 32-bit machines; what about 64-bit...?) - raise ValueError("The height limit must be < 21.4.") + int_bits = sys.maxsize.bit_length() + max_height_limit = int_bits * 0.693147 # log(2.0) = 0.693147 approx + if height_limit >= max_height_limit: + raise ValueError("The height limit must be < {} = {}log(2) on a {}-bit machine.".format(max_height_limit, int_bits, int_bits+1)) moduli_option = 0 # Use Stoll's sieving program... see strategies in ratpoints-1.4.c @@ -1352,5 +1326,4 @@ class mwrank_MordellWeil(SageObject): [[1, -1, 1], [-2, 3, 1], [-14, 25, 8]] """ - L = eval(self.__mw.getbasis().replace(":",",")) - return [[Integer(x), Integer(y), Integer(z)] for (x,y,z) in L] + return self.__mw.getbasis() diff --git a/src/sage/libs/eclib/mwrank.pyx b/src/sage/libs/eclib/mwrank.pyx index b82831d..ce5090c 100644 --- a/src/sage/libs/eclib/mwrank.pyx +++ b/src/sage/libs/eclib/mwrank.pyx @@ -28,6 +28,7 @@ from cysignals.signals cimport sig_on, sig_off from sage.cpython.string cimport char_to_str, str_to_bytes from sage.cpython.string import FS_ENCODING from sage.libs.eclib cimport bigint, Curvedata, mw, two_descent +from sage.rings.all import Integer cdef extern from "wrap.cpp": ### misc functions ### @@ -55,8 +56,8 @@ cdef extern from "wrap.cpp": char* mw_getbasis(mw* m) double mw_regulator(mw* m) int mw_rank(mw* m) - int mw_saturate(mw* m, bigint* index, char** unsat, - long sat_bd, int odd_primes_only) + int mw_saturate(mw* m, long* index, char** unsat, + long sat_bd, long sat_low_bd) void mw_search(mw* m, char* h_lim, int moduli_option, int verb) ### two_descent ### @@ -67,8 +68,7 @@ cdef extern from "wrap.cpp": long two_descent_get_rank(two_descent* t) long two_descent_get_rank_bound(two_descent* t) long two_descent_get_selmer_rank(two_descent* t) - void two_descent_saturate(two_descent* t, long sat_bd) - + void two_descent_saturate(two_descent* t, long sat_bd, long sat_low_bd) cdef object string_sigoff(char* s): sig_off() @@ -445,7 +445,6 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class -1269581104000000 """ sig_on() - from sage.rings.all import Integer return Integer(string_sigoff(Curvedata_getdiscr(self.x))) def conductor(self): @@ -467,7 +466,6 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class 126958110400 """ sig_on() - from sage.rings.all import Integer return Integer(string_sigoff(Curvedata_conductor(self.x))) def isogeny_class(self, verbose=False): @@ -503,6 +501,36 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class ############# _mw ################# +def parse_point_list(s): + r""" + Parse a string representing a list of points. + + INPUT: + + - ``s`` (string) -- string representation of a list of points, for + example '[]', '[[1:2:3]]', or '[[1:2:3],[4:5:6]]'. + + OUTPUT: + + (list) a list of triples of integers, for example [], [[1,2,3]], [[1,2,3],[4,5,6]]. + + EXAMPLES:: + + sage: from sage.libs.eclib.mwrank import parse_point_list + sage: parse_point_list('[]') + [] + sage: parse_point_list('[[1:2:3]]') + [[1, 2, 3]] + sage: parse_point_list('[[1:2:3],[4:5:6]]') + [[1, 2, 3], [4, 5, 6]] + + """ + s = s.replace(":", ",").replace(" ", "") + if s == '[]': + return [] + pts = s[2:-2].split('],[') + return [[Integer(x) for x in pt.split(",")] for pt in pts] + cdef class _mw: """ Cython class wrapping eclib's mw class. @@ -561,72 +589,37 @@ cdef class _mw: sage: EQ.search(1) P1 = [0:1:0] is torsion point, order 1 P1 = [-3:0:1] is generator number 1 - ... - P4 = [12:35:27] = 1*P1 + -1*P2 + -1*P3 (mod torsion) - - The previous command produces the following output:: - - P1 = [0:1:0] is torsion point, order 1 - P1 = [-3:0:1] is generator number 1 - saturating up to 20...Checking 2-saturation - Points have successfully been 2-saturated (max q used = 7) - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 7) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 23) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 41) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 17) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 43) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 31) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 37) + saturating up to 20...Saturation index bound (for points of good reduction) = 3 + Reducing saturation bound from given value 20 to computed index bound 3 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 7) + Checking 3-saturation + Points were proved 3-saturated (max q used = 7) done P2 = [-2:3:1] is generator number 2 - saturating up to 20...Checking 2-saturation + saturating up to 20...Saturation index bound (for points of good reduction) = 4 + Reducing saturation bound from given value 20 to computed index bound 4 + Checking saturation at [ 2 3 ] + Checking 2-saturation possible kernel vector = [1,1] This point may be in 2E(Q): [14:-52:1] - ...and it is! + ...and it is! Replacing old generator #1 with new generator [1:-1:1] + Reducing index bound from 4 to 2 Points have successfully been 2-saturated (max q used = 7) Index gain = 2^1 - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 13) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 67) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 53) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 73) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 103) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 113) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 47) - done (index = 2). + done, index = 2. Gained index 2, new generators = [ [1:-1:1] [-2:3:1] ] P3 = [-14:25:8] is generator number 3 - saturating up to 20...Checking 2-saturation - Points have successfully been 2-saturated (max q used = 11) - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 13) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 71) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 101) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 127) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 151) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 139) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 179) - done (index = 1). + saturating up to 20...Saturation index bound (for points of good reduction) = 3 + Reducing saturation bound from given value 20 to computed index bound 3 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 11) + Checking 3-saturation + Points were proved 3-saturated (max q used = 13) + done, index = 1. P4 = [-1:3:1] = -1*P1 + -1*P2 + -1*P3 (mod torsion) P4 = [0:2:1] = 2*P1 + 0*P2 + 1*P3 (mod torsion) P4 = [2:13:8] = -3*P1 + 1*P2 + -1*P3 (mod torsion) @@ -687,7 +680,7 @@ cdef class _mw: sig_on() return string_sigoff(mw_getbasis(self.x)) - def process(self, point, sat=0): + def process(self, point, saturation_bound=0): """ Processes the given point, adding it to the mw group. @@ -697,10 +690,12 @@ cdef class _mw: An ``ArithmeticError`` is raised if the point is not on the curve. - - ``sat`` (int, default 0) --saturate at primes up to ``sat``. - No saturation is done if ``sat=0``. (Note that it is more - efficient to add several points at once and then saturate - just once at the end). + - ``saturation_bound`` (int, default 0) --saturate at primes up to ``saturation_bound``. + No saturation is done if ``saturation_bound=0``. If ``saturation_bound=-1`` then + saturation is done at all primes, by computing a bound on + the saturation index. Note that it is more efficient to add + several points at once and then saturate just once at the + end. .. NOTE:: @@ -746,7 +741,7 @@ cdef class _mw: cdef _bigint x,y,z sig_on() x,y,z = _bigint(point[0]), _bigint(point[1]), _bigint(point[2]) - r = mw_process(self.curve, self.x, x.x, y.x, z.x, sat) + r = mw_process(self.curve, self.x, x.x, y.x, z.x, saturation_bound) sig_off() if r != 0: raise ArithmeticError("point (=%s) not on curve." % point) @@ -757,8 +752,8 @@ cdef class _mw: OUTPUT: - (string) String representation of the points in the basis of - the mw group. + (list) list of integer triples giving the projective + coordinates of the points in the basis. EXAMPLES:: @@ -768,13 +763,13 @@ cdef class _mw: sage: EQ = _mw(E) sage: EQ.search(3) sage: EQ.getbasis() - '[[0:-1:1], [-1:1:1]]' + [[0, -1, 1], [-1, 1, 1]] sage: EQ.rank() 2 """ sig_on() s = string_sigoff(mw_getbasis(self.x)) - return s + return parse_point_list(s) def regulator(self): """ @@ -797,7 +792,7 @@ cdef class _mw: sage: EQ = _mw(E) sage: EQ.search(3) sage: EQ.getbasis() - '[[0:-1:1], [-1:1:1]]' + [[0, -1, 1], [-1, 1, 1]] sage: EQ.rank() 2 sage: EQ.regulator() @@ -824,39 +819,54 @@ cdef class _mw: sage: EQ = _mw(E) sage: EQ.search(3) sage: EQ.getbasis() - '[[0:-1:1], [-1:1:1]]' + [[0, -1, 1], [-1, 1, 1]] sage: EQ.rank() 2 """ sig_on() r = mw_rank(self.x) sig_off() - from sage.rings.all import Integer return Integer(r) - def saturate(self, int sat_bd=-1, int odd_primes_only=0): + def saturate(self, int sat_bd=-1, int sat_low_bd=2): """ Saturates the current subgroup of the mw group. INPUT: - - ``sat_bnd`` (int, default -1) -- bound on primes at which to - saturate. If -1 (default), compute a bound for the primes - which may not be saturated, and use that. + - ``sat_bnd`` (int, default -1) -- upper bound on primes at + which to saturate. If -1 (default), compute a bound for the + primes which may not be saturated, and use that. Otherwise, + the bound used is the minumum of the value of ``sat_bnd`` + and the computed bound. - - ``odd_primes_only`` (bool, default ``False``) -- only do - saturation at odd primes. (If the points have been found - via 2-descent they should already be 2-saturated.) + - ``sat_low_bd`` (int, default 2) -- only do saturation at + prime not less than this. For exampe, if the points have + been found via 2-descent they should already be 2-saturated, + and ``sat_low_bd=3`` is appropriate. OUTPUT: (tuple) (success flag, index, list) The success flag will be 1 unless something failed (usually an indication that the points - were not saturated but the precision is not high enough to - divide out successfully). The index is the index of the mw - group before saturation in the mw group after. The list is a - string representation of the primes at which saturation was - not proved or achieved. + were not saturated but eclib was not able to divide out + successfully). The index is the index of the mw group before + saturation in the mw group after. The list is a string + representation of the primes at which saturation was not + proved or achieved. + + .. NOTE:: + + ``eclib`` will compute a bound on the saturation index. If + the computed saturation bound is very large and ``sat_bnd`` is + -1, ``eclib`` may output a warning, but will still attempt to + saturate up to the computed bound. If a positive value of + ``sat_bnd`` is given which is greater than the computed bound, + `p`-saturation will only be carried out for primes up to the + compated bound. Setting ``sat_low_bnd`` to a value greater + than 2 allows for saturation to be done incrementally, or for + exactly one prime `p` by setting both ``sat_bd`` and + ``sat_low_bd`` to `p`. EXAMPLES:: @@ -872,34 +882,23 @@ cdef class _mw: sage: EQ [[-1:1:1]] - If we set the saturation bound at 2, then saturation will fail:: + If we set the saturation bound at 2, then saturation will not + enlarge the basis, but the success flag is still 1 (True) + since we did not ask to check 3-saturation:: sage: EQ = _mw(E) sage: EQ.process([494, -5720, 6859]) # 3 times another point sage: EQ.saturate(sat_bd=2) - Saturation index bound = 10 - WARNING: saturation at primes p > 2 will not be done; - points may be unsaturated at primes between 2 and index bound - Failed to saturate MW basis at primes [ ] - (0, 1, '[ ]') + (1, 1, '[ ]') sage: EQ [[494:-5720:6859]] - The following output is also seen in the preceding example:: - - Saturation index bound = 10 - WARNING: saturation at primes p > 2 will not be done; - points may be unsaturated at primes between 2 and index bound - Failed to saturate MW basis at primes [ ] - - """ - cdef _bigint index + cdef long index cdef char* s cdef int ok sig_on() - index = _bigint() - ok = mw_saturate(self.x, index.x, &s, sat_bd, odd_primes_only) + ok = mw_saturate(self.x, &index, &s, sat_bd, sat_low_bd) unsat = string_sigoff(s) return ok, index, unsat @@ -1094,7 +1093,6 @@ cdef class _two_descent: sig_on() r = two_descent_get_rank(self.x) sig_off() - from sage.rings.all import Integer return Integer(r) def getrankbound(self): @@ -1128,7 +1126,6 @@ cdef class _two_descent: sig_on() r = two_descent_get_rank_bound(self.x) sig_off() - from sage.rings.all import Integer return Integer(r) def getselmer(self): @@ -1161,7 +1158,6 @@ cdef class _two_descent: sig_on() r = two_descent_get_selmer_rank(self.x) sig_off() - from sage.rings.all import Integer return Integer(r) def ok(self): @@ -1222,10 +1218,21 @@ cdef class _two_descent: """ return two_descent_get_certain(self.x) - def saturate(self, saturation_bound=0): + def saturate(self, saturation_bound=0, lower=3): """ Carries out saturation of the points found by a 2-descent. + INPUT: + + - ``saturation_bound`` (int) -- an upper bound on the primes + `p` at which `p`-saturation will be carried out, or -1, in + which case ``eclib`` will compute an upper bound on the + saturation index. + + - ``lower`` (int, default 3) -- do no `p`-saturation for `p` + less than this. The default is 3 since the points found + during 2-descent will be 2-saturated. + OUTPUT: None. @@ -1257,7 +1264,7 @@ cdef class _two_descent: '[[1:-1:1], [-2:3:1], [-14:25:8]]' """ sig_on() - two_descent_saturate(self.x, saturation_bound) + two_descent_saturate(self.x, saturation_bound, 3) sig_off() def getbasis(self): diff --git a/src/sage/libs/eclib/newforms.pyx b/src/sage/libs/eclib/newforms.pyx index b50b606..96263cd 100644 --- a/src/sage/libs/eclib/newforms.pyx +++ b/src/sage/libs/eclib/newforms.pyx @@ -140,6 +140,7 @@ cdef class ECModularSymbol: - ``nap`` - (int, default 1000): the number of ap of E to use in determining the normalisation of the modular symbols. + Note that eclib will increase this to 100*sqrt(N) if necessary. EXAMPLES:: diff --git a/src/sage/libs/eclib/t b/src/sage/libs/eclib/t new file mode 100644 index 00000000..e69de29 --- /dev/null +++ b/src/sage/libs/eclib/t diff --git a/src/sage/libs/eclib/wrap.cpp b/src/sage/libs/eclib/wrap.cpp index 58c18ab..28e6da8 100644 --- a/src/sage/libs/eclib/wrap.cpp +++ b/src/sage/libs/eclib/wrap.cpp @@ -178,11 +178,11 @@ int mw_rank(struct mw* m) } /* Returns index and unsat long array, which user must deallocate */ -int mw_saturate(struct mw* m, bigint* index, char** unsat, - long sat_bd, int odd_primes_only) +int mw_saturate(struct mw* m, long* index, char** unsat, + long sat_bd, long sat_low_bd) { vector v; - int s = m->saturate(*index, v, sat_bd, odd_primes_only); + int s = m->saturate(*index, v, sat_bd, sat_low_bd); ostringstream instore; instore << v; *unsat = stringstream_to_char(instore); @@ -236,9 +236,9 @@ long two_descent_get_certain(const two_descent* t) return t->getcertain(); } -void two_descent_saturate(struct two_descent* t, long sat_bd) +void two_descent_saturate(struct two_descent* t, long sat_bd, long sat_low_bd) { - t->saturate(sat_bd); + t->saturate(sat_bd, sat_low_bd); } double two_descent_regulator(struct two_descent* t) diff --git a/src/sage/schemes/elliptic_curves/ell_modular_symbols.py b/src/sage/schemes/elliptic_curves/ell_modular_symbols.py index a32f64e..30a61e1 100644 --- a/src/sage/schemes/elliptic_curves/ell_modular_symbols.py +++ b/src/sage/schemes/elliptic_curves/ell_modular_symbols.py @@ -298,19 +298,27 @@ class ModularSymbolECLIB(ModularSymbol): sage: m(0) 1/5 - If ``nap`` is too small, the normalization in eclib may be incorrect. See :trac:`31317`:: + If ``nap`` is too small, the normalization in eclib used to be + incorrect (see :trac:`31317`), but since ``eclib`` version + v20210310 the value of ``nap`` is increased automatically by + ``eclib``:: sage: from sage.schemes.elliptic_curves.ell_modular_symbols import ModularSymbolECLIB sage: E = EllipticCurve('1590g1') sage: m = ModularSymbolECLIB(E, sign=+1, nap=300) sage: [m(a/5) for a in [1..4]] - [1001/153, -1001/153, -1001/153, 1001/153] + [13/2, -13/2, -13/2, 13/2] - Those values are incorrect. The correct values are:: + These values are correct, and increasing ``nap`` has no + effect. The correct values may verified by the numerical + implementation:: sage: m = ModularSymbolECLIB(E, sign=+1, nap=400) sage: [m(a/5) for a in [1..4]] [13/2, -13/2, -13/2, 13/2] + sage: m = E.modular_symbol(implementation='num') + sage: [m(a/5) for a in [1..4]] + [13/2, -13/2, -13/2, 13/2] """ from sage.libs.eclib.newforms import ECModularSymbol diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index a792afc..5a56389 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -779,7 +779,7 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): sage: E = EllipticCurve('11a1') sage: EE = E.mwrank_curve() sage: EE - y^2+ y = x^3 - x^2 - 10*x - 20 + y^2 + y = x^3 - x^2 - 10 x - 20 sage: type(EE) sage: EE.isogeny_class() @@ -1283,22 +1283,21 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): sage: [Mminus(1/i) for i in [1..11]] [0, 0, 1/2, 1/2, 0, 0, -1/2, -1/2, 0, 0, 0] - With the default 'eclib' implementation, if ``nap`` is too - small, the normalization may be computed incorrectly. See - :trac:`31317`:: + With older version of eclib, in the default 'eclib' + implementation, if ``nap`` is too small, the normalization may + be computed incorrectly (see :trac:`31317`). This was fixed + in eclib version v20210310, since now eclib increase ``nap`` + automatically. The following used to give incorrect results. + See :trac:`31443`:: sage: E = EllipticCurve('1590g1') sage: m = E.modular_symbol(nap=300) sage: [m(a/5) for a in [1..4]] - [1001/153, -1001/153, -1001/153, 1001/153] + [13/2, -13/2, -13/2, 13/2] - Those values are incorrect. The correct values may be - obtained by increasing ``nap``, as verified by the numerical + These values are correct, as verified by the numerical implementation:: - sage: m = E.modular_symbol(nap=400) - sage: [m(a/5) for a in [1..4]] - [13/2, -13/2, -13/2, 13/2] sage: m = E.modular_symbol(implementation='num') sage: [m(a/5) for a in [1..4]] [13/2, -13/2, -13/2, 13/2] @@ -2525,7 +2524,7 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): assert reg.parent() is R return reg - def saturation(self, points, verbose=False, max_prime=0, odd_primes_only=False): + def saturation(self, points, verbose=False, max_prime=-1, min_prime=2): """ Given a list of rational points on E, compute the saturation in E(Q) of the subgroup they generate. @@ -2538,17 +2537,24 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): - ``verbose (bool)`` - (default: ``False``), if ``True``, give verbose output - - ``max_prime (int)`` - (default: 0), saturation is - performed for all primes up to max_prime. If max_prime==0, - perform saturation at *all* primes, i.e., compute the true - saturation. + - ``max_prime`` (int, default -1) -- If `-1` (the default), an + upper bound is computed for the primes at which the subgroup + may not be saturated, and saturation is performed for all + primes up to this bound. Otherwise, the bound used is the + minimum of ``max_prime`` and the computed bound. - - ``odd_primes_only (bool)`` - only do saturation at - odd primes + - ``min_prime (int)`` - (default: 2), only do `p`-saturation + at primes `p` greater than or equal to this. + .. note:: - OUTPUT: + To saturate at a single prime `p`, set ``max_prime`` and + ``min_prime`` both to `p`. One situation where this is + useful is after mapping saturated points from another + elliptic curve by a `p`-isogeny, since the images may not + be `p`-saturated but with be saturated at all other primes. + OUTPUT: - ``saturation (list)`` - points that form a basis for the saturation @@ -2559,12 +2565,32 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): - ``regulator (real with default precision)`` - regulator of saturated points. + ALGORITHM: Uses Cremona's ``eclib`` package, which computes a + bound on the saturation index. To `p`-saturate, or prove + `p`-saturation, we consider the reductions of the points + modulo primes `q` of good reduction such that `E(\FF_q)` has + order divisible by `p`. + + .. note:: + + In versons of ``eclib`` up to ``v20190909``, division of + points in ``eclib`` was done using floating point methods, + without automatic handling of precision, so that + `p`-saturation sometimes failed unless + ``mwrank_set_precision()`` was called in advance with a + suitably high bit precision. Since version ``v20210310`` + of ``eclib``, division is done using exact methods based on + division polynomials, and `p`-saturation cannot fail in + this way. + + .. note:: + + The computed index of saturation may be large, in which + case saturation may take a long time. For example, the + rank 4 curve ``EllipticCurve([0,1,1,-9872,374262])`` has a + saturation index bound of 86682 and takes around 15 minutes + to prove saturation. - ALGORITHM: Uses Cremona's ``mwrank`` package. With ``max_prime=0``, - we call ``mwrank`` with successively larger prime bounds until the full - saturation is provably found. The results of saturation at the - previous primes is stored in each case, so this should be - reasonably fast. EXAMPLES:: @@ -2577,7 +2603,9 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): TESTS: - See :trac:`10590`. This example would loop forever at default precision:: + See :trac:`10590`. With ``eclib`` versions up to + ``v20190909``, this example would loop forever at default + precision. Since version ``v20210310`` it runs fine:: sage: E = EllipticCurve([1, 0, 1, -977842, -372252745]) sage: P = E([-192128125858676194585718821667542660822323528626273/336995568430319276695106602174283479617040716649, 70208213492933395764907328787228427430477177498927549075405076353624188436/195630373799784831667835900062564586429333568841391304129067339731164107, 1]) @@ -2585,7 +2613,7 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): 113.302910926080 sage: E.saturation([P]) ([(-192128125858676194585718821667542660822323528626273/336995568430319276695106602174283479617040716649 : 70208213492933395764907328787228427430477177498927549075405076353624188436/195630373799784831667835900062564586429333568841391304129067339731164107 : 1)], 1, 113.302910926080) - sage: (Q,), ind, reg = E.saturation([2*P]) # needs higher precision, handled by eclib + sage: (Q,), ind, reg = E.saturation([2*P]) sage: 2*Q == 2*P True sage: ind @@ -2634,36 +2662,16 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): c = Emin.mwrank_curve() from sage.libs.eclib.all import mwrank_MordellWeil mw = mwrank_MordellWeil(c, verbose) - mw.process(v) - repeat_until_saturated = False - if max_prime == 0: - repeat_until_saturated = True - max_prime = 9973 - from sage.libs.all import mwrank_get_precision, mwrank_set_precision - prec0 = mwrank_get_precision() - prec = 100 - if prec0