Skip to content

Commit cecd9a2

Browse files
author
Release Manager
committed
gh-36806: add generic has_order() function Currently there is `order_from_multiple()` for generic groups, but sometimes we really only want to know if the order equals a particular value (e.g., when sampling random elements to find a generating set of a group with known structure). This means the computation can be aborted earlier than when finding the exact order is required. In this patch we add such an optimized `has_order()` function as well as a convenience wrapper for elliptic-curve points. An example of the speedup can be found in one of the new doctests. URL: #36806 Reported by: Lorenz Panny Reviewer(s): Lorenz Panny, Travis Scrimshaw
2 parents c9d0134 + 3e61e7d commit cecd9a2

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

src/sage/groups/generic.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,8 @@ def linear_relation(P, Q, operation='+', identity=None, inverse=None, op=None):
11811181
# 2. order_from_bounds: finds the order given an interval containing a
11821182
# multiple of the order
11831183
#
1184+
# 3. has_order: check if the order is exactly equal to a given integer
1185+
#
11841186
################################################################
11851187

11861188

@@ -1408,6 +1410,100 @@ def order_from_bounds(P, bounds, d=None, operation='+',
14081410

14091411
return order_from_multiple(P, m, operation=operation, check=False)
14101412

1413+
def has_order(P, n, operation='+'):
1414+
r"""
1415+
Generic function to test if a group element `P` has order
1416+
exactly equal to a given positive integer `n`.
1417+
1418+
INPUT:
1419+
1420+
- ``P`` -- group element with respect to the specified ``operation``
1421+
- ``n`` -- positive integer, or its :class:`~sage.structure.factorization.Factorization`
1422+
- ``operation`` -- string, either ``'+'`` (default) or ``'*'``
1423+
1424+
EXAMPLES::
1425+
1426+
sage: from sage.groups.generic import has_order
1427+
sage: E.<P> = EllipticCurve(GF(71), [5,5])
1428+
sage: P.order()
1429+
57
1430+
sage: has_order(P, 57)
1431+
True
1432+
sage: has_order(P, factor(57))
1433+
True
1434+
sage: has_order(P, 19)
1435+
False
1436+
sage: has_order(3*P, 19)
1437+
True
1438+
sage: has_order(3*P, 57)
1439+
False
1440+
1441+
::
1442+
1443+
sage: R = Zmod(14981)
1444+
sage: g = R(321)
1445+
sage: g.multiplicative_order()
1446+
42
1447+
sage: has_order(g, 42, operation='*')
1448+
True
1449+
sage: has_order(g, factor(42), operation='*')
1450+
True
1451+
sage: has_order(g, 70, operation='*')
1452+
False
1453+
1454+
TESTS::
1455+
1456+
sage: ns = [randrange(1,10**5) for _ in range(randrange(1,5))]
1457+
sage: A = AdditiveAbelianGroup(ns)
1458+
sage: from sage.groups.generic import has_order
1459+
sage: el = A.random_element()
1460+
sage: o = el.order()
1461+
sage: has_order(el, o)
1462+
True
1463+
sage: has_order(el, o.factor())
1464+
True
1465+
sage: has_order(el, ZZ(randrange(100)*o + randrange(o)))
1466+
False
1467+
1468+
.. NOTE::
1469+
1470+
In some cases, order *testing* can be much faster than
1471+
*computing* the order using :func:`order_from_multiple`.
1472+
"""
1473+
if isinstance(n, sage.rings.integer.Integer):
1474+
n = n.factor()
1475+
1476+
G = P.parent()
1477+
if operation in addition_names:
1478+
isid = lambda el: not el
1479+
mult = lambda el, n: multiple(el, n, operation='+')
1480+
elif operation in multiplication_names:
1481+
isid = lambda el: el.is_one()
1482+
mult = multiple
1483+
else:
1484+
raise ValueError('unknown group operation')
1485+
1486+
def _rec(Q, fn):
1487+
if not fn:
1488+
return isid(Q)
1489+
1490+
if len(fn) == 1:
1491+
p, k = fn[0]
1492+
for _ in range(k):
1493+
if isid(Q):
1494+
return False
1495+
Q = mult(Q, p)
1496+
return isid(Q)
1497+
1498+
fl = fn[::2]
1499+
fr = fn[1::2]
1500+
l = prod(p**k for p,k in fl)
1501+
r = prod(p**k for p,k in fr)
1502+
L, R = mult(Q, r), mult(Q, l)
1503+
return _rec(L, fl) and _rec(R, fr)
1504+
1505+
return _rec(P, n)
1506+
14111507

14121508
def merge_points(P1, P2, operation='+',
14131509
identity=None, inverse=None, op=None, check=True):

src/sage/schemes/elliptic_curves/ell_point.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,104 @@ def __bool__(self):
539539
"""
540540
return bool(self[2])
541541

542+
def has_order(self, n):
543+
r"""
544+
Test if this point has order exactly `n`.
545+
546+
INPUT:
547+
548+
- ``n`` -- integer, or its :class:`~sage.structure.factorization.Factorization`
549+
550+
ALGORITHM:
551+
552+
Compare a cached order if available, otherwise use :func:`sage.groups.generic.has_order`.
553+
554+
EXAMPLES::
555+
556+
sage: E = EllipticCurve('26b1')
557+
sage: P = E(1, 0)
558+
sage: P.has_order(7)
559+
True
560+
sage: P._order
561+
7
562+
sage: P.has_order(7)
563+
True
564+
565+
It also works with a :class:`~sage.structure.factorization.Factorization` object::
566+
567+
sage: E = EllipticCurve(GF(419), [1,0])
568+
sage: P = E(-33, 8)
569+
sage: P.has_order(factor(21))
570+
True
571+
sage: P._order
572+
21
573+
sage: P.has_order(factor(21))
574+
True
575+
576+
This method can be much faster than computing the order and comparing::
577+
578+
sage: # not tested -- timings are different each time
579+
sage: p = 4 * prod(primes(3,377)) * 587 - 1
580+
sage: E = EllipticCurve(GF(p), [1,0])
581+
sage: %timeit P = E.random_point(); P.set_order(multiple=p+1)
582+
72.4 ms ± 773 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
583+
sage: %timeit P = E.random_point(); P.has_order(p+1)
584+
32.8 ms ± 3.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
585+
sage: fac = factor(p+1)
586+
sage: %timeit P = E.random_point(); P.has_order(fac)
587+
30.6 ms ± 3.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
588+
589+
The order is cached once it has been confirmed once, and the cache is
590+
shared with :meth:`order`::
591+
592+
sage: # not tested -- timings are different each time
593+
sage: P, = E.gens()
594+
sage: delattr(P, '_order')
595+
sage: %time P.has_order(p+1)
596+
CPU times: user 83.6 ms, sys: 30 µs, total: 83.6 ms
597+
Wall time: 83.8 ms
598+
True
599+
sage: %time P.has_order(p+1)
600+
CPU times: user 31 µs, sys: 2 µs, total: 33 µs
601+
Wall time: 37.9 µs
602+
True
603+
sage: %time P.order()
604+
CPU times: user 11 µs, sys: 0 ns, total: 11 µs
605+
Wall time: 16 µs
606+
5326738796327623094747867617954605554069371494832722337612446642054009560026576537626892113026381253624626941643949444792662881241621373288942880288065660
607+
sage: delattr(P, '_order')
608+
sage: %time P.has_order(fac)
609+
CPU times: user 68.6 ms, sys: 17 µs, total: 68.7 ms
610+
Wall time: 68.7 ms
611+
True
612+
sage: %time P.has_order(fac)
613+
CPU times: user 92 µs, sys: 0 ns, total: 92 µs
614+
Wall time: 97.5 µs
615+
True
616+
sage: %time P.order()
617+
CPU times: user 10 µs, sys: 1e+03 ns, total: 11 µs
618+
Wall time: 14.5 µs
619+
5326738796327623094747867617954605554069371494832722337612446642054009560026576537626892113026381253624626941643949444792662881241621373288942880288065660
620+
621+
TESTS::
622+
623+
sage: E = EllipticCurve([1,2,3,4,5])
624+
sage: E(0).has_order(1)
625+
True
626+
sage: E(0).has_order(Factorization([]))
627+
True
628+
"""
629+
if hasattr(self, '_order'): # already known
630+
if not isinstance(n, Integer):
631+
n = n.value()
632+
return self._order == n
633+
ret = generic.has_order(self, n, operation='+')
634+
if ret and not hasattr(self, '_order'): # known now; cache
635+
if not isinstance(n, Integer):
636+
n = n.value()
637+
self._order = n
638+
return ret
639+
542640
def has_finite_order(self):
543641
"""
544642
Return ``True`` if this point has finite additive order as an

0 commit comments

Comments
 (0)