Skip to content

Commit 59a67bb

Browse files
author
Release Manager
committed
gh-40317: Implement partial integer factorization using flint As in the title. This is more efficient than trial division, which is the only currently implemented partial integer factorization algorithm. Side note: it may be possible to reuse `limit=` argument and set `bits=limit.bit_length()`, but this *kind of* break backwards compatibility. (or maybe not?) ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #40317 Reported by: user202729 Reviewer(s): Sahil Jain, Travis Scrimshaw, user202729
2 parents edb1ce9 + f09192f commit 59a67bb

File tree

5 files changed

+55
-23
lines changed

5 files changed

+55
-23
lines changed

src/sage/arith/misc.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2560,7 +2560,7 @@ def trial_division(n, bound=None):
25602560
return ZZ(n).trial_division(bound)
25612561

25622562

2563-
def factor(n, proof=None, int_=False, algorithm='pari', verbose=0, **kwds):
2563+
def factor(n, proof=None, int_=False, algorithm=None, verbose=0, **kwds):
25642564
"""
25652565
Return the factorization of ``n``. The result depends on the
25662566
type of ``n``.
@@ -2734,6 +2734,11 @@ def factor(n, proof=None, int_=False, algorithm='pari', verbose=0, **kwds):
27342734
27352735
sage: len(factor(2^2203-1,proof=false))
27362736
1
2737+
2738+
Test ``limit``::
2739+
2740+
sage: factor(2990, limit=10)
2741+
2 * 5 * 299
27372742
"""
27382743
try:
27392744
m = n.factor

src/sage/matrix/matrix_integer_dense.pyx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2978,6 +2978,10 @@ cdef class Matrix_integer_dense(Matrix_dense):
29782978
precision=precision)
29792979

29802980
R = A.to_matrix(self.new_matrix())
2981+
2982+
else:
2983+
raise ValueError("unknown algorithm")
2984+
29812985
return R
29822986

29832987
def LLL(self, delta=None, eta=None, algorithm='fpLLL:wrapper', fp=None, prec=0, early_red=False, use_givens=False, use_siegel=False, transformation=False, **kwds):

src/sage/misc/sageinspect.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,7 @@ def sage_getargspec(obj):
14481448
annotations={})
14491449
sage: sage_getargspec(factor)
14501450
FullArgSpec(args=['n', 'proof', 'int_', 'algorithm', 'verbose'],
1451-
varargs=None, varkw='kwds', defaults=(None, False, 'pari', 0),
1451+
varargs=None, varkw='kwds', defaults=(None, False, None, 0),
14521452
kwonlyargs=[], kwonlydefaults=None, annotations={})
14531453
14541454
In the case of a class or a class instance, the :class:`FullArgSpec` of the
@@ -1808,7 +1808,7 @@ def sage_signature(obj):
18081808
sage: sage_signature(identity_matrix) # needs sage.modules
18091809
<Signature (ring, n=0, sparse=False)>
18101810
sage: sage_signature(factor)
1811-
<Signature (n, proof=None, int_=False, algorithm='pari', verbose=0, **kwds)>
1811+
<Signature (n, proof=None, int_=False, algorithm=None, verbose=0, **kwds)>
18121812
18131813
In the case of a class or a class instance, the :class:`Signature` of the
18141814
``__new__``, ``__init__`` or ``__call__`` method is returned::
@@ -2671,8 +2671,8 @@ def __internal_tests():
26712671
26722672
A cython function with default arguments (one of which is a string)::
26732673
2674-
sage: sage_getdef(sage.rings.integer.Integer.factor, obj_name='factor')
2675-
"factor(algorithm='pari', proof=None, limit=None, int_=False, verbose=0)"
2674+
sage: sage_getdef(sage.rings.integer.Integer.binomial, obj_name='binomial')
2675+
"binomial(m, algorithm='gmp')"
26762676
26772677
This used to be problematic, but was fixed in :issue:`10094`::
26782678

src/sage/rings/factorint_flint.pyx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ from sage.libs.flint.fmpz_factor_sage cimport *
2424
from sage.rings.integer cimport Integer
2525

2626

27-
def factor_using_flint(Integer n):
27+
def factor_using_flint(Integer n, unsigned bits=0):
2828
r"""
2929
Factor the nonzero integer ``n`` using FLINT.
3030
@@ -35,6 +35,7 @@ def factor_using_flint(Integer n):
3535
INPUT:
3636
3737
- ``n`` -- a nonzero sage Integer; the number to factor
38+
- ``bits`` -- if nonzero, passed to ``fmpz_factor_smooth``
3839
3940
OUTPUT:
4041
@@ -89,7 +90,10 @@ def factor_using_flint(Integer n):
8990
fmpz_factor_init(factors)
9091

9192
sig_on()
92-
fmpz_factor(factors, p)
93+
if bits:
94+
fmpz_factor_smooth(factors, p, bits, 0) # TODO make proved=* customizable
95+
else:
96+
fmpz_factor(factors, p)
9397
sig_off()
9498

9599
pairs = fmpz_factor_to_pairlist(factors)

src/sage/rings/integer.pyx

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3912,8 +3912,8 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
39123912
sig_off()
39133913
return x
39143914

3915-
def factor(self, algorithm='pari', proof=None, limit=None, int_=False,
3916-
verbose=0):
3915+
def factor(self, algorithm=None, proof=None, limit=None, int_=False,
3916+
verbose=0, *, flint_bits=None):
39173917
"""
39183918
Return the prime factorization of this integer as a
39193919
formal Factorization object.
@@ -3932,7 +3932,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
39323932
- ``'magma'`` -- use the MAGMA computer algebra system (requires
39333933
an installation of MAGMA)
39343934
3935-
- ``'qsieve'`` -- use Bill Hart's quadratic sieve code;
3935+
- ``'qsieve'`` -- use ``qsieve_factor`` in the FLINT library;
39363936
WARNING: this may not work as expected, see qsieve? for
39373937
more information
39383938
@@ -3946,6 +3946,10 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
39463946
given it must fit in a ``signed int``, and the factorization is done
39473947
using trial division and primes up to limit
39483948
3949+
- ``flint_bits`` -- integer or ``None`` (default: ``None``); if specified,
3950+
perform only a partial factorization, primes at most ``2^flint_bits``
3951+
have a high probability of being detected
3952+
39493953
OUTPUT: a Factorization object containing the prime factors and
39503954
their multiplicities
39513955
@@ -3994,6 +3998,13 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
39943998
sage: n.factor(algorithm='flint') # needs sage.libs.flint
39953999
2 * 3 * 11 * 13 * 41 * 73 * 22650083 * 1424602265462161
39964000
4001+
Example with ``flint_bits``. The small prime factor is found since it is
4002+
much smaller than `2^{50}`, but not the large ones::
4003+
4004+
sage: n = next_prime(2^256) * next_prime(2^257) * next_prime(2^40)
4005+
sage: n.factor(algorithm='flint', flint_bits=50) # needs sage.libs.flint
4006+
1099511627791 * 2681...6291
4007+
39974008
We factor using a quadratic sieve algorithm::
39984009
39994010
sage: # needs sage.libs.pari
@@ -4021,14 +4032,11 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
40214032
sage: n.factor(algorithm='foobar')
40224033
Traceback (most recent call last):
40234034
...
4024-
ValueError: Algorithm is not known
4035+
ValueError: algorithm is not known
40254036
"""
40264037
from sage.structure.factorization import Factorization
40274038
from sage.structure.factorization_integer import IntegerFactorization
40284039

4029-
if algorithm not in ['pari', 'flint', 'kash', 'magma', 'qsieve', 'ecm']:
4030-
raise ValueError("Algorithm is not known")
4031-
40324040
cdef Integer n, p, unit
40334041

40344042
if mpz_sgn(self.value) == 0:
@@ -4039,18 +4047,27 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
40394047
unit = one
40404048
else:
40414049
n = PY_NEW(Integer)
4042-
unit = PY_NEW(Integer)
40434050
mpz_neg(n.value, self.value)
4044-
mpz_set_si(unit.value, -1)
4045-
4046-
if mpz_cmpabs_ui(n.value, 1) == 0:
4047-
return IntegerFactorization([], unit=unit, unsafe=True,
4048-
sort=False, simplify=False)
4051+
unit = smallInteger(-1)
40494052

40504053
if limit is not None:
4054+
if algorithm is not None:
4055+
raise ValueError('trial division will always be used when limit is provided')
40514056
from sage.rings.factorint import factor_trial_division
40524057
return factor_trial_division(self, limit)
40534058

4059+
if algorithm is None:
4060+
algorithm = 'pari'
4061+
elif algorithm not in ['pari', 'flint', 'kash', 'magma', 'qsieve', 'ecm']:
4062+
raise ValueError("algorithm is not known")
4063+
4064+
if algorithm != 'flint' and flint_bits is not None:
4065+
raise ValueError("cannot specify flint_bits when algorithm is not flint")
4066+
4067+
if n.is_one():
4068+
return IntegerFactorization([], unit=unit, unsafe=True,
4069+
sort=False, simplify=False)
4070+
40544071
if mpz_fits_slong_p(n.value):
40554072
global n_factor_to_list
40564073
if n_factor_to_list is None:
@@ -4080,7 +4097,9 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
40804097
sort=False, simplify=False)
40814098
elif algorithm == 'flint':
40824099
from sage.rings.factorint_flint import factor_using_flint
4083-
F = factor_using_flint(n)
4100+
if flint_bits is not None and flint_bits <= 0:
4101+
raise ValueError('flint_bits must be positive or None')
4102+
F = factor_using_flint(n, flint_bits or 0)
40844103
F.sort()
40854104
return IntegerFactorization(F, unit=unit, unsafe=True,
40864105
sort=False, simplify=False)
@@ -6698,13 +6717,13 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
66986717
if not mpz_sgn(n.value):
66996718
mpz_set_ui(t.value, 0)
67006719
mpz_abs(g.value, self.value)
6701-
mpz_set_si(s.value, 1 if mpz_sgn(self.value) >= 0 else -1)
6720+
s = smallInteger(1 if mpz_sgn(self.value) >= 0 else -1)
67026721
return g, s, t
67036722

67046723
if not mpz_sgn(self.value):
67056724
mpz_set_ui(s.value, 0)
67066725
mpz_abs(g.value, n.value)
6707-
mpz_set_si(t.value, 1 if mpz_sgn(n.value) >= 0 else -1)
6726+
t = smallInteger(1 if mpz_sgn(n.value) >= 0 else -1)
67086727
return g, s, t
67096728

67106729
# both n and self are nonzero, so we need to do a division and

0 commit comments

Comments
 (0)