diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index 8fe6803bb7a..a7031580a33 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -451,6 +451,15 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): AUTHOR: - John Cremona (2008-03-15) + + TESTS: + + Ensures Python integers work:: + + sage: from sage.groups.generic import bsgs + sage: b = Mod(2,37); a = b^20 + sage: bsgs(b, a, (0r, 36r)) + 20 """ Z = integer_ring.ZZ @@ -470,6 +479,8 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): raise ValueError("identity, inverse and operation must be given") lb, ub = bounds + lb = Z(lb) + ub = Z(ub) if lb < 0 or ub < lb: raise ValueError("bsgs() requires 0<=lb<=ub") @@ -1378,7 +1389,9 @@ def order_from_bounds(P, bounds, d=None, operation='+', - ``P`` -- a Sage object which is a group element - ``bounds`` -- a 2-tuple ``(lb,ub)`` such that ``m*P=0`` (or - ``P**m=1``) for some ``m`` with ``lb<=m<=ub`` + ``P**m=1``) for some ``m`` with ``lb<=m<=ub``. If ``None``, + gradually increasing bounds will be tried (might loop infinitely + if the element has no torsion). - ``d`` -- (optional) a positive integer; only ``m`` which are multiples of this will be considered @@ -1407,6 +1420,8 @@ def order_from_bounds(P, bounds, d=None, operation='+', sage: b = a^4 sage: order_from_bounds(b, (5^4, 5^5), operation='*') 781 + sage: order_from_bounds(b, None, operation='*') + 781 sage: # needs sage.rings.finite_rings sage.schemes sage: E = EllipticCurve(k, [2,4]) @@ -1424,6 +1439,15 @@ def order_from_bounds(P, bounds, d=None, operation='+', sage: order_from_bounds(w, (200, 250), operation='*') 23 """ + if bounds is None: + lb = 1 + ub = 256 + while True: + try: + return order_from_bounds(P, (lb, ub), d, operation, identity, inverse, op) + except ValueError: + lb = ub + 1 + ub *= 16 from operator import mul, add if operation in multiplication_names: diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 262c90d4aeb..1b02baeb241 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -739,19 +739,33 @@ def __pari__(self): else: return pari([0]) - def order(self): + def order(self, algorithm=None): r""" Return the order of this point on the elliptic curve. If the point is zero, returns 1, otherwise raise a :exc:`NotImplementedError`. - For curves over number fields and finite fields, see below. + For curves over number fields and finite fields, see + :meth:`EllipticCurvePoint_number_field.order` and + :meth:`EllipticCurvePoint_finite_field.order` respectively. .. NOTE:: :meth:`additive_order` is a synonym for :meth:`order` + INPUT: + + - ``algorithm`` -- string (default: ``None``) -- the algorithm to use, + can be ``'pari'``, ``'generic'``, ``'generic_small'`` or ``'hybrid'``. + ``'generic_small'`` may be preferable when the order of the point + is very small compared to the order of the torsion, + and the order of the torsion is hard to factorize. + ``'hybrid'`` uses a combination of ``'pari'`` and ``'generic_small'`` + to ensure the complexity of computing the order is + roughly the square root of the order, and that it is still fast + if the order only have very small prime factors. + EXAMPLES:: sage: K. = FractionField(PolynomialRing(QQ,'t')) @@ -760,8 +774,7 @@ def order(self): sage: P.order() Traceback (most recent call last): ... - NotImplementedError: Computation of order of a point not implemented - over general fields. + NotImplementedError: algorithm None not implemented for order of a point on an elliptic curve over general fields sage: E(0).additive_order() 1 sage: E(0).order() == 1 @@ -772,8 +785,49 @@ def order(self): if self.is_zero(): self._order = Integer(1) return self._order - raise NotImplementedError("Computation of order of a point " - "not implemented over general fields.") + self._order = self._compute_order(algorithm) + return self._order + + def _compute_order(self, algorithm): + """ + Internal method to compute the order of this point. Used by :meth:`order`. + Subclasses may override this method. ``self`` is guaranteed to be nonzero. + The implementation of :meth:`order` takes care of the :meth:`is_zero` case and + caching of :attr:`_order`. + + TESTS:: + + sage: K. = FractionField(PolynomialRing(QQ,'t')) + sage: E = EllipticCurve([0, 0, 0, -t^2, 0]) + sage: P = E(t,0) + sage: P._compute_order(algorithm='generic_small') + 2 + sage: P._compute_order(algorithm=None) + Traceback (most recent call last): + ... + NotImplementedError: algorithm None not implemented for order of a point on an elliptic curve over general fields + """ + if algorithm == 'generic_small': + return generic.order_from_bounds(self, None) + elif algorithm == 'hybrid': + lb = 1 + sqrt_ub = 32 + N = None + while True: + if N is None and sqrt_ub >= 5000: + N = self.curve().order() + if isinstance(N, Integer): + factorization = N.factor(limit=sqrt_ub) + if factorization.is_complete_factorization(): + return self._compute_order(algorithm='pari') + try: + ub = sqrt_ub**2 + return generic.order_from_bounds(self, (lb, ub)) + except ValueError: + lb = ub + 1 + sqrt_ub *= 4 + raise NotImplementedError(f"algorithm {algorithm!r} not implemented for " + "order of a point on an elliptic curve over general fields") additive_order = order @@ -911,8 +965,7 @@ def has_finite_order(self): sage: P.has_finite_order() Traceback (most recent call last): ... - NotImplementedError: Computation of order of a point not implemented - over general fields. + NotImplementedError: algorithm None not implemented for order of a point on an elliptic curve over general fields sage: (2*P).is_zero() True """ @@ -941,7 +994,7 @@ def has_infinite_order(self): sage: P.has_infinite_order() Traceback (most recent call last): ... - NotImplementedError: Computation of order of a point not implemented over general fields. + NotImplementedError: algorithm None not implemented for order of a point on an elliptic curve over general fields sage: (2*P).is_zero() True """ @@ -2717,7 +2770,7 @@ class EllipticCurvePoint_number_field(EllipticCurvePoint_field): True """ - def order(self): + def order(self, algorithm=None): r""" Return the order of this point on the elliptic curve. @@ -2729,6 +2782,18 @@ def order(self): :meth:`additive_order` is a synonym for :meth:`order` + INPUT: + + - ``algorithm`` -- string (default: ``None``) -- the algorithm to use, + can be ``'pari'``, ``'generic'``, ``'generic_small'`` or ``'hybrid'``. + ``'generic_small'`` may be preferable when the order of the point + is very small compared to the order of the torsion, + and the order of the torsion is hard to factorize. + ``'hybrid'`` uses a combination of ``'pari'`` and ``'generic_small'`` + to ensure the complexity of computing the order is + roughly the square root of the order, and that it is still fast + if the order only have very small prime factors. + EXAMPLES:: sage: E = EllipticCurve([0,0,1,-1,0]) @@ -2746,47 +2811,68 @@ def order(self): sage: P.additive_order() 2 """ - try: - return self._order - except AttributeError: - pass + return super().order(algorithm) - if self.is_zero(): - self._order = Integer(1) - return self._order + def _compute_order(self, algorithm): + """ + TESTS:: + sage: E = EllipticCurve([0,0,1,-1,0]) + sage: P = E([0,0]); P + (0 : 0 : 1) + sage: P._compute_order(algorithm='pari') + +Infinity + sage: P._compute_order(algorithm='generic') + +Infinity + sage: P._compute_order(algorithm='unknown') + Traceback (most recent call last): + ... + NotImplementedError: unknown algorithm 'unknown' + + sage: E = EllipticCurve([1, 2]) + sage: E(1, 2).order(algorithm='generic') + 4 + sage: E(1, -2).order(algorithm='hybrid') + 4 + """ E = self.curve() - # First try PARI - try: + if algorithm == 'pari': n = E.pari_curve().ellorder(self) if n: - n = Integer(n) + return Integer(n) else: - n = oo - self._order = n - return n - except PariError: - pass + return oo - # Get the torsion order if known, else a bound on (multiple - # of) the order. We do not compute the torsion if it is not - # already known, since computing the bound is faster (and is - # also cached). + if algorithm == 'generic': + # Get the torsion order if known, else a bound on (multiple + # of) the order. We do not compute the torsion if it is not + # already known, since computing the bound is faster (and is + # also cached). + try: + N = E._torsion_order + except AttributeError: + N = E._torsion_bound() - try: - N = E._torsion_order - except AttributeError: - N = E._torsion_bound() + # Now self is a torsion point iff it is killed by N: + if not (N*self).is_zero(): + return oo - # Now self is a torsion point iff it is killed by N: - if not (N*self).is_zero(): - self._order = oo - return self._order + # Finally we find the exact order using the generic code: + return generic.order_from_multiple(self, N, operation='+') - # Finally we find the exact order using the generic code: - self._order = generic.order_from_multiple(self, N, operation='+') - return self._order + if algorithm in ('generic_small', 'hybrid'): + return super()._compute_order(algorithm) + + if algorithm is not None: + raise NotImplementedError(f"unknown algorithm {algorithm!r}") + + # First try PARI + try: + return self._compute_order('pari') + except PariError: + pass + return self._compute_order('generic') additive_order = order @@ -4649,7 +4735,7 @@ def has_finite_order(self): """ return True - def order(self): + def order(self, algorithm=None): r""" Return the order of this point on the elliptic curve. @@ -4659,6 +4745,18 @@ def order(self): :meth:`additive_order` is a synonym for :meth:`order` + INPUT: + + - ``algorithm`` -- string (default: ``None``) -- the algorithm to use, + can be ``'pari'``, ``'generic'``, ``'generic_small'`` or ``'hybrid'``. + ``'generic_small'`` may be preferable when the order of the point + is very small compared to the order of the torsion, + and the order of the torsion is hard to factorize. + ``'hybrid'`` uses a combination of ``'pari'`` and ``'generic_small'`` + to ensure the complexity of computing the order is + roughly the square root of the order, and that it is still fast + if the order only have very small prime factors. + EXAMPLES:: sage: # needs sage.rings.finite_rings @@ -4710,8 +4808,30 @@ def order(self): sage: P.order() # random 46912611635760 + Tests ``algorithm='generic_small'``:: + + sage: # needs sage.rings.finite_rings + sage: p = next_prime(2^256) + sage: q = next_prime(p) + sage: E = EllipticCurve(GF(660*p*q-1), [1, 0]) + sage: P = E.lift_x(11) * p * q + sage: P.order() # not tested (pari will try to factor p*q which takes forever) + sage: P.order(algorithm='generic_small') + 330 + sage: P.order() # works due to caching + 330 + TESTS: + Tests ``algorithm='hybrid'``:: + + sage: # needs sage.rings.finite_rings + sage: P.order(algorithm='hybrid') + 330 + sage: E = EllipticCurve(GF(60*2^200-1), [1, 0]) + sage: E.0._compute_order(algorithm='hybrid') == 60*2^200 + True + Check that the order actually gets cached (:issue:`32786`):: sage: # needs sage.rings.finite_rings @@ -4730,19 +4850,38 @@ def order(self): sage: E._order # needs sage.rings.finite_rings 31298 """ - try: - return self._order - except AttributeError: - pass + return super().order(algorithm) + + def _compute_order(self, algorithm): + """ + TESTS:: + sage: # needs sage.rings.finite_rings + sage: E = EllipticCurve(GF(31337), [42, 1]) + sage: P = E.lift_x(1) + sage: P._compute_order('pari') + 15649 + sage: P._compute_order('generic_small') + 15649 + sage: P._compute_order('unknown') + Traceback (most recent call last): + ... + NotImplementedError: algorithm 'unknown' not implemented for order of a point on an elliptic curve over finite fields + """ E = self.curve() - if getattr(E, '_order', None) is None: - # The curve order will be computed and cached by PARI during - # ellorder() anyway. We might as well cache it here too. - E._order = Integer(E.pari_curve().ellcard()) + if algorithm == 'pari' or algorithm is None: + if getattr(E, '_order', None) is None: + # The curve order will be computed and cached by PARI during + # ellorder() anyway. We might as well cache it here too. + E._order = Integer(E.pari_curve().ellcard()) - self._order = Integer(E.pari_curve().ellorder(self, E._order)) - return self._order + return Integer(E.pari_curve().ellorder(self, E._order)) + + if algorithm in ('generic_small', 'hybrid'): + return super()._compute_order(algorithm) + + raise NotImplementedError(f"algorithm {algorithm!r} not implemented for " + "order of a point on an elliptic curve over finite fields") additive_order = order diff --git a/src/sage/structure/factorization.py b/src/sage/structure/factorization.py index 9b1e00e56d8..b075b00d3f5 100644 --- a/src/sage/structure/factorization.py +++ b/src/sage/structure/factorization.py @@ -1420,3 +1420,24 @@ def radical_value(self): raise ValueError("all exponents in the factorization must be positive") from sage.misc.misc_c import prod return prod([p for p, _ in self.__x]) + + def is_complete_factorization(self): + """ + Return whether this factorization is a complete rather than + a partial factorization, i.e., whether all the bases + are irreducible. + + EXAMPLES:: + + sage: F = 143.factor(limit=9); F + 143 + sage: F.is_complete_factorization() + False + sage: F = 143.factor(limit=12); F + 11 * 13 + sage: F.is_complete_factorization() + True + sage: factor(-2006).is_complete_factorization() + True + """ + return all(p.is_irreducible() or p.is_unit() for p, _ in self.__x)