Python (con PyPy JIT v1.9) ~ 1.9s
Utilizzo di un setaccio quadratico polinomiale multiplo . Ho preso questo come una sfida del codice, quindi ho deciso di non utilizzare alcuna libreria esterna (a parte la log
funzione standard , suppongo). Durante il cronometraggio, dovrebbe essere usato PyPy JIT , poiché risulta in tempi 4-5 volte più veloci di quello di cPython .
Aggiornamento (29/07/2013):
dalla pubblicazione originale, ho apportato diverse modifiche minori, ma significative che aumentano la velocità complessiva di un fattore di circa 2,5 volte.
Aggiornamento (27-08-2014):
poiché questo post sta ancora ricevendo attenzione, ho aggiornato my_math.py
correggendo due errori, per chiunque possa utilizzarlo:
isqrt
era difettoso, a volte produceva output errato per valori molto vicini a un quadrato perfetto. Questo è stato corretto e le prestazioni sono aumentate usando un seme molto migliore.
is_prime
è stato aggiornato. Il mio precedente tentativo di rimuovere i 2 sprps quadrati perfetti era insensibile, nella migliore delle ipotesi. Ho aggiunto un controllo a 3 sprp - una tecnica utilizzata da Mathmatica - per garantire che il valore testato sia privo di quadrati.
Aggiornamento (24-11-2014):
se alla fine del calcolo non vengono rilevate congruenze non banali, il programma ora setaccia polinomi aggiuntivi. Questo era precedentemente contrassegnato nel codice come TODO
.
mpqs.py
from my_math import *
from math import log
from time import clock
from argparse import ArgumentParser
# Multiple Polynomial Quadratic Sieve
def mpqs(n, verbose=False):
if verbose:
time1 = clock()
root_n = isqrt(n)
root_2n = isqrt(n+n)
# formula chosen by experimentation
# seems to be close to optimal for n < 10^50
bound = int(5 * log(n, 10)**2)
prime = []
mod_root = []
log_p = []
num_prime = 0
# find a number of small primes for which n is a quadratic residue
p = 2
while p < bound or num_prime < 3:
# legendre (n|p) is only defined for odd p
if p > 2:
leg = legendre(n, p)
else:
leg = n & 1
if leg == 1:
prime += [p]
mod_root += [int(mod_sqrt(n, p))]
log_p += [log(p, 10)]
num_prime += 1
elif leg == 0:
if verbose:
print 'trial division found factors:'
print p, 'x', n/p
return p
p = next_prime(p)
# size of the sieve
x_max = len(prime)*60
# maximum value on the sieved range
m_val = (x_max * root_2n) >> 1
# fudging the threshold down a bit makes it easier to find powers of primes as factors
# as well as partial-partial relationships, but it also makes the smoothness check slower.
# there's a happy medium somewhere, depending on how efficient the smoothness check is
thresh = log(m_val, 10) * 0.735
# skip small primes. they contribute very little to the log sum
# and add a lot of unnecessary entries to the table
# instead, fudge the threshold down a bit, assuming ~1/4 of them pass
min_prime = int(thresh*3)
fudge = sum(log_p[i] for i,p in enumerate(prime) if p < min_prime)/4
thresh -= fudge
if verbose:
print 'smoothness bound:', bound
print 'sieve size:', x_max
print 'log threshold:', thresh
print 'skipping primes less than:', min_prime
smooth = []
used_prime = set()
partial = {}
num_smooth = 0
num_used_prime = 0
num_partial = 0
num_poly = 0
root_A = isqrt(root_2n / x_max)
if verbose:
print 'sieving for smooths...'
while True:
# find an integer value A such that:
# A is =~ sqrt(2*n) / x_max
# A is a perfect square
# sqrt(A) is prime, and n is a quadratic residue mod sqrt(A)
while True:
root_A = next_prime(root_A)
leg = legendre(n, root_A)
if leg == 1:
break
elif leg == 0:
if verbose:
print 'dumb luck found factors:'
print root_A, 'x', n/root_A
return root_A
A = root_A * root_A
# solve for an adequate B
# B*B is a quadratic residue mod n, such that B*B-A*C = n
# this is unsolvable if n is not a quadratic residue mod sqrt(A)
b = mod_sqrt(n, root_A)
B = (b + (n - b*b) * mod_inv(b + b, root_A))%A
# B*B-A*C = n <=> C = (B*B-n)/A
C = (B*B - n) / A
num_poly += 1
# sieve for prime factors
sums = [0.0]*(2*x_max)
i = 0
for p in prime:
if p < min_prime:
i += 1
continue
logp = log_p[i]
inv_A = mod_inv(A, p)
# modular root of the quadratic
a = int(((mod_root[i] - B) * inv_A)%p)
b = int(((p - mod_root[i] - B) * inv_A)%p)
k = 0
while k < x_max:
if k+a < x_max:
sums[k+a] += logp
if k+b < x_max:
sums[k+b] += logp
if k:
sums[k-a+x_max] += logp
sums[k-b+x_max] += logp
k += p
i += 1
# check for smooths
i = 0
for v in sums:
if v > thresh:
x = x_max-i if i > x_max else i
vec = set()
sqr = []
# because B*B-n = A*C
# (A*x+B)^2 - n = A*A*x*x+2*A*B*x + B*B - n
# = A*(A*x*x+2*B*x+C)
# gives the congruency
# (A*x+B)^2 = A*(A*x*x+2*B*x+C) (mod n)
# because A is chosen to be square, it doesn't need to be sieved
val = sieve_val = A*x*x + 2*B*x + C
if sieve_val < 0:
vec = set([-1])
sieve_val = -sieve_val
for p in prime:
while sieve_val%p == 0:
if p in vec:
# keep track of perfect square factors
# to avoid taking the sqrt of a gigantic number at the end
sqr += [p]
vec ^= set([p])
sieve_val = int(sieve_val / p)
if sieve_val == 1:
# smooth
smooth += [(vec, (sqr, (A*x+B), root_A))]
used_prime |= vec
elif sieve_val in partial:
# combine two partials to make a (xor) smooth
# that is, every prime factor with an odd power is in our factor base
pair_vec, pair_vals = partial[sieve_val]
sqr += list(vec & pair_vec) + [sieve_val]
vec ^= pair_vec
smooth += [(vec, (sqr + pair_vals[0], (A*x+B)*pair_vals[1], root_A*pair_vals[2]))]
used_prime |= vec
num_partial += 1
else:
# save partial for later pairing
partial[sieve_val] = (vec, (sqr, A*x+B, root_A))
i += 1
num_smooth = len(smooth)
num_used_prime = len(used_prime)
if verbose:
print 100 * num_smooth / num_prime, 'percent complete\r',
if num_smooth > num_used_prime:
if verbose:
print '%d polynomials sieved (%d values)'%(num_poly, num_poly*x_max*2)
print 'found %d smooths (%d from partials) in %f seconds'%(num_smooth, num_partial, clock()-time1)
print 'solving for non-trivial congruencies...'
used_prime_list = sorted(list(used_prime))
# set up bit fields for gaussian elimination
masks = []
mask = 1
bit_fields = [0]*num_used_prime
for vec, vals in smooth:
masks += [mask]
i = 0
for p in used_prime_list:
if p in vec: bit_fields[i] |= mask
i += 1
mask <<= 1
# row echelon form
col_offset = 0
null_cols = []
for col in xrange(num_smooth):
pivot = col-col_offset == num_used_prime or bit_fields[col-col_offset] & masks[col] == 0
for row in xrange(col+1-col_offset, num_used_prime):
if bit_fields[row] & masks[col]:
if pivot:
bit_fields[col-col_offset], bit_fields[row] = bit_fields[row], bit_fields[col-col_offset]
pivot = False
else:
bit_fields[row] ^= bit_fields[col-col_offset]
if pivot:
null_cols += [col]
col_offset += 1
# reduced row echelon form
for row in xrange(num_used_prime):
# lowest set bit
mask = bit_fields[row] & -bit_fields[row]
for up_row in xrange(row):
if bit_fields[up_row] & mask:
bit_fields[up_row] ^= bit_fields[row]
# check for non-trivial congruencies
for col in null_cols:
all_vec, (lh, rh, rA) = smooth[col]
lhs = lh # sieved values (left hand side)
rhs = [rh] # sieved values - n (right hand side)
rAs = [rA] # root_As (cofactor of lhs)
i = 0
for field in bit_fields:
if field & masks[col]:
vec, (lh, rh, rA) = smooth[i]
lhs += list(all_vec & vec) + lh
all_vec ^= vec
rhs += [rh]
rAs += [rA]
i += 1
factor = gcd(list_prod(rAs)*list_prod(lhs) - list_prod(rhs), n)
if factor != 1 and factor != n:
break
else:
if verbose:
print 'none found.'
continue
break
if verbose:
print 'factors found:'
print factor, 'x', n/factor
print 'time elapsed: %f seconds'%(clock()-time1)
return factor
if __name__ == "__main__":
parser =ArgumentParser(description='Uses a MPQS to factor a composite number')
parser.add_argument('composite', metavar='number_to_factor', type=long,
help='the composite number to factor')
parser.add_argument('--verbose', dest='verbose', action='store_true',
help="enable verbose output")
args = parser.parse_args()
if args.verbose:
mpqs(args.composite, args.verbose)
else:
time1 = clock()
print mpqs(args.composite)
print 'time elapsed: %f seconds'%(clock()-time1)
my_math.py
# divide and conquer list product
def list_prod(a):
size = len(a)
if size == 1:
return a[0]
return list_prod(a[:size>>1]) * list_prod(a[size>>1:])
# greatest common divisor of a and b
def gcd(a, b):
while b:
a, b = b, a%b
return a
# modular inverse of a mod m
def mod_inv(a, m):
a = int(a%m)
x, u = 0, 1
while a:
x, u = u, x - (m/a)*u
m, a = a, m%a
return x
# legendre symbol (a|m)
# note: returns m-1 if a is a non-residue, instead of -1
def legendre(a, m):
return pow(a, (m-1) >> 1, m)
# modular sqrt(n) mod p
# p must be prime
def mod_sqrt(n, p):
a = n%p
if p%4 == 3:
return pow(a, (p+1) >> 2, p)
elif p%8 == 5:
v = pow(a << 1, (p-5) >> 3, p)
i = ((a*v*v << 1) % p) - 1
return (a*v*i)%p
elif p%8 == 1:
# Shank's method
q = p-1
e = 0
while q&1 == 0:
e += 1
q >>= 1
n = 2
while legendre(n, p) != p-1:
n += 1
w = pow(a, q, p)
x = pow(a, (q+1) >> 1, p)
y = pow(n, q, p)
r = e
while True:
if w == 1:
return x
v = w
k = 0
while v != 1 and k+1 < r:
v = (v*v)%p
k += 1
if k == 0:
return x
d = pow(y, 1 << (r-k-1), p)
x = (x*d)%p
y = (d*d)%p
w = (w*y)%p
r = k
else: # p == 2
return a
#integer sqrt of n
def isqrt(n):
c = n*4/3
d = c.bit_length()
a = d>>1
if d&1:
x = 1 << a
y = (x + (n >> a)) >> 1
else:
x = (3 << a) >> 2
y = (x + (c >> a)) >> 1
if x != y:
x = y
y = (x + n/x) >> 1
while y < x:
x = y
y = (x + n/x) >> 1
return x
# strong probable prime
def is_sprp(n, b=2):
if n < 2: return False
d = n-1
s = 0
while d&1 == 0:
s += 1
d >>= 1
x = pow(b, d, n)
if x == 1 or x == n-1:
return True
for r in xrange(1, s):
x = (x * x)%n
if x == 1:
return False
elif x == n-1:
return True
return False
# lucas probable prime
# assumes D = 1 (mod 4), (D|n) = -1
def is_lucas_prp(n, D):
P = 1
Q = (1-D) >> 2
# n+1 = 2**r*s where s is odd
s = n+1
r = 0
while s&1 == 0:
r += 1
s >>= 1
# calculate the bit reversal of (odd) s
# e.g. 19 (10011) <=> 25 (11001)
t = 0
while s:
if s&1:
t += 1
s -= 1
else:
t <<= 1
s >>= 1
# use the same bit reversal process to calculate the sth Lucas number
# keep track of q = Q**n as we go
U = 0
V = 2
q = 1
# mod_inv(2, n)
inv_2 = (n+1) >> 1
while t:
if t&1:
# U, V of n+1
U, V = ((U + V) * inv_2)%n, ((D*U + V) * inv_2)%n
q = (q * Q)%n
t -= 1
else:
# U, V of n*2
U, V = (U * V)%n, (V * V - 2 * q)%n
q = (q * q)%n
t >>= 1
# double s until we have the 2**r*sth Lucas number
while r:
U, V = (U * V)%n, (V * V - 2 * q)%n
q = (q * q)%n
r -= 1
# primality check
# if n is prime, n divides the n+1st Lucas number, given the assumptions
return U == 0
# primes less than 212
small_primes = set([
2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97,101,103,107,109,113,
127,131,137,139,149,151,157,163,167,173,
179,181,191,193,197,199,211])
# pre-calced sieve of eratosthenes for n = 2, 3, 5, 7
indices = [
1, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
89, 97,101,103,107,109,113,121,127,131,
137,139,143,149,151,157,163,167,169,173,
179,181,187,191,193,197,199,209]
# distances between sieve values
offsets = [
10, 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6,
6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4,
2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6,
4, 2, 4, 6, 2, 6, 4, 2, 4, 2,10, 2]
max_int = 2147483647
# an 'almost certain' primality check
def is_prime(n):
if n < 212:
return n in small_primes
for p in small_primes:
if n%p == 0:
return False
# if n is a 32-bit integer, perform full trial division
if n <= max_int:
i = 211
while i*i < n:
for o in offsets:
i += o
if n%i == 0:
return False
return True
# Baillie-PSW
# this is technically a probabalistic test, but there are no known pseudoprimes
if not is_sprp(n, 2): return False
# idea shamelessly stolen from Mathmatica
# if n is a 2-sprp and a 3-sprp, n is necessarily square-free
if not is_sprp(n, 3): return False
a = 5
s = 2
# if n is a perfect square, this will never terminate
while legendre(a, n) != n-1:
s = -s
a = s-a
return is_lucas_prp(n, a)
# next prime strictly larger than n
def next_prime(n):
if n < 2:
return 2
# first odd larger than n
n = (n + 1) | 1
if n < 212:
while True:
if n in small_primes:
return n
n += 2
# find our position in the sieve rotation via binary search
x = int(n%210)
s = 0
e = 47
m = 24
while m != e:
if indices[m] < x:
s = m
m = (s + e + 1) >> 1
else:
e = m
m = (s + e) >> 1
i = int(n + (indices[m] - x))
# adjust offsets
offs = offsets[m:] + offsets[:m]
while True:
for o in offs:
if is_prime(i):
return i
i += o
I / O di esempio:
$ pypy mpqs.py --verbose 94968915845307373740134800567566911
smoothness bound: 6117
sieve size: 24360
log threshold: 14.3081031579
skipping primes less than: 47
sieving for smooths...
144 polynomials sieved (7015680 values)
found 405 smooths (168 from partials) in 0.513794 seconds
solving for non-trivial congruencies...
factors found:
216366620575959221 x 438925910071081891
time elapsed: 0.685765 seconds
$ pypy mpqs.py --verbose 523022617466601111760007224100074291200000001
smoothness bound: 9998
sieve size: 37440
log threshold: 15.2376302725
skipping primes less than: 59
sieving for smooths...
428 polynomials sieved (32048640 values)
found 617 smooths (272 from partials) in 1.912131 seconds
solving for non-trivial congruencies...
factors found:
14029308060317546154181 x 37280713718589679646221
time elapsed: 2.064387 seconds
Nota: il mancato utilizzo --verbose
dell'opzione darà tempi leggermente migliori:
$ pypy mpqs.py 94968915845307373740134800567566911
216366620575959221
time elapsed: 0.630235 seconds
$ pypy mpqs.py 523022617466601111760007224100074291200000001
14029308060317546154181
time elapsed: 1.886068 seconds
Concetti basilari
In generale, un setaccio quadratico si basa sulla seguente osservazione: qualsiasi composito dispari n può essere rappresentato come:
Non è molto difficile da confermare. Poiché n è dispari, la distanza tra due cofattori di n deve essere pari a 2d , dove x è il punto medio tra di loro. Inoltre, la stessa relazione vale per qualsiasi multiplo di n
Si noti che se si trova tale x e d , si otterrà immediatamente un fattore (non necessariamente primo) di n , poiché x + d e x - d dividono entrambi n per definizione. Questa relazione può essere ulteriormente indebolita - a seguito di consentire potenziali banali congruenze - alla seguente forma:
Quindi, in generale, se possiamo trovare due quadrati perfetti che sono equivalenti a mod n , allora è abbastanza probabile che possiamo produrre direttamente un fattore di n a la gcd (x ± d, n) . Sembra piuttosto semplice, vero?
Tranne che non lo è. Se intendessimo effettuare una ricerca esaustiva su tutte le possibili x , avremmo bisogno di cercare l'intero intervallo da [ √ n , √ ( 2n ) ], che è leggermente più piccolo della divisione di prova completa, ma richiede anche un'operazione costosa per is_square
ogni iterazione confermare il valore di d . A meno che non si sappia in anticipo che n ha fattori molto vicini a √ n , è probabile che la divisione di prova sia più veloce.
Forse possiamo indebolire ulteriormente questa relazione. Supponiamo di aver scelto una x , tale che per
una fattorizzazione primaria completa di y è prontamente nota. Se avessimo avuto abbastanza relazioni del genere, dovremmo essere in grado di costruire una d adeguata , se scegliamo un numero di y tale che il loro prodotto sia un quadrato perfetto; cioè, tutti i fattori primi sono usati un numero pari di volte. In effetti, se disponiamo di un numero tale di y maggiore del numero totale di fattori primi unici che contengono, una soluzione è garantita; Diventa un sistema di equazioni lineari. La domanda ora diventa: come abbiamo scelto tale x ? È qui che entra in gioco il setaccio.
The Sieve
Considera il polinomio:
Quindi per ogni primo p e intero k , vale quanto segue:
Questo significa che dopo aver risolto le radici del mod polinomiale p - cioè, hai trovato una x tale che y (x) ≡ 0 (mod p) , ergo y è divisibile per p - quindi hai trovato un numero infinito di tale x . In questo modo, puoi setacciare un intervallo di x , identificando piccoli fattori primi di y , sperando di trovarne alcuni per i quali tutti i fattori primi sono piccoli. Numeri noti come k-smooth , dove k è il fattore primo più grande utilizzato.
Tuttavia, ci sono alcuni problemi con questo approccio. Non tutti i valori di x sono adeguati, infatti ce ne sono solo pochi, centrati attorno a √ n . Valori più piccoli diventeranno in gran parte negativi (a causa del termine -n ) e valori più grandi diventeranno troppo grandi, in modo tale che è improbabile che la loro fattorizzazione in numeri primi sia costituita solo da piccoli numeri primi. Ci sarà un certo numero di tali x , ma a meno che il composito che stai prendendo in considerazione sia molto piccolo, è altamente improbabile che troverai abbastanza smooth per provocare una fattorizzazione. E così, per n più grandi , diventa necessario setacciare più polinomi di una data forma.
Polinomi multipli
Quindi abbiamo bisogno di più polinomi per setacciare? Cosa ne pensi di questo:
Funzionerà. Nota che A e B potrebbero letteralmente essere qualsiasi valore intero e la matematica è ancora valida. Tutto quello che dobbiamo fare è scegliere alcuni valori casuali, risolvere la radice del polinomio e setacciare i valori vicini a zero. A questo punto potremmo chiamarlo abbastanza buono: se lanci abbastanza pietre in direzioni casuali, prima o poi sarai costretto a rompere una finestra.
Tranne, c'è un problema anche con quello. Se la pendenza del polinomio è grande all'intercetta x, che sarà se non è relativamente piatta, ci saranno solo alcuni valori adatti da setacciare per polinomio. Funzionerà, ma finirai per setacciare molti polinomi prima di ottenere ciò di cui hai bisogno. Possiamo fare di meglio?
Possiamo fare di meglio. Un'osservazione, a seguito di Montgomery è la seguente: se A e B sono scelti in modo tale che esista qualche C soddisfacente
quindi l'intero polinomio può essere riscritto come
Inoltre, se A viene scelto per essere un quadrato perfetto, il termine A iniziale può essere trascurato durante la setacciatura, risultando in valori molto più piccoli e una curva molto più piatta. Perché una tale soluzione esista, n deve essere un residuo quadratico mod √ A , che può essere immediatamente riconosciuto calcolando il simbolo Legendre :
( n | √A ) = 1 . Si noti che per risolvere per B , è necessario conoscere una fattorizzazione primaria completa di √A (al fine di prendere la radice quadrata modulare √n (mod √A) ), motivo per cui √A viene in genere scelto come primo.
Si può quindi dimostrare che if , quindi per tutti i valori di x ∈ [ -M, M ] :
E ora, finalmente, abbiamo tutti i componenti necessari per implementare il nostro setaccio. O noi?
Poteri dei Primi come Fattori
Il nostro setaccio, come descritto sopra, ha un grosso difetto. Può identificare quali valori di x comporteranno una y divisibile per p , ma non può identificare se questa y sia divisibile per una potenza di p . Per determinarlo, dovremmo eseguire la divisione di prova sul valore da setacciare, fino a quando non sarà più divisibile per p . Sembrava che avessimo raggiunto un impassé: l'intero punto del setaccio era che non dovevamo farlo. È ora di controllare il playbook.
Sembra abbastanza utile. Se la somma di ln di tutti i piccoli fattori primi di y è vicina al valore atteso di ln (y) , allora è quasi scontato che y non abbia altri fattori. Inoltre, se modifichiamo un po 'il valore atteso, possiamo anche identificare valori come smooth che hanno diversi poteri di numeri primi come fattori. In questo modo, possiamo utilizzare il setaccio come processo di "pre-screening" e considerare solo quei valori che possono essere fluidi.
Questo ha anche alcuni altri vantaggi. Nota che i piccoli numeri primi contribuiscono ben poco alla ln somma, ma tuttavia richiedono il tempo più setaccio. La setacciatura del valore 3 richiede più tempo di 11, 13, 17, 19 e 23 combinati . Invece, possiamo semplicemente saltare i primi numeri primi e regolare la soglia di conseguenza, supponendo che una certa percentuale di questi sarebbe passata.
Un altro risultato è che un certo numero di valori potrà "scivolare", che sono per lo più fluidi, ma contengono un unico grande cofattore. Potremmo semplicemente scartare questi valori, ma supponiamo di aver trovato un altro valore per lo più regolare, con lo stesso cofattore. Possiamo quindi usare questi due valori per costruire una y utilizzabile ; poiché il loro prodotto conterrà questo grande cofattore al quadrato, non è più necessario considerarlo.
Mettere tutto insieme
L'ultima cosa che dobbiamo fare è usare questi valori di y per costruire una x e d adeguata . Supponiamo di considerare solo i fattori non quadrati di y , cioè i fattori primi di un potere dispari. Quindi, ogni y può essere espresso nel modo seguente:
che può essere espresso sotto forma di matrice:
Il problema diventa quindi trovare un vettore v tale che vM = ⦳ (mod 2) , dove ⦳ è il vettore null. Cioè, per risolvere per lo spazio nullo fianco di M . Questo può essere fatto in diversi modi, il più semplice dei quali è eseguire l'eliminazione gaussiana su M T , sostituendo l'operazione di aggiunta di riga con una riga xor . Ciò comporterà un numero di vettori con base spazio nullo, qualsiasi combinazione di questi produrrà una soluzione valida.
La costruzione di x è abbastanza semplice. È semplicemente il prodotto di Ax + B per ciascuna delle y utilizzate. La costruzione di d è leggermente più complicata. Se dovessimo prendere il prodotto di tutti y , finiremmo con un valore con 10s di migliaia, se non 100s di migliaia di cifre, per cui dobbiamo trovare la radice quadrata. Questo calcolo è poco costoso. Invece, possiamo tenere traccia dei poteri uniformi dei numeri primi durante il processo di setacciatura, e quindi utilizzare le operazioni and e xor sui vettori di fattori non quadrati per ricostruire la radice quadrata.
Mi sembra di aver raggiunto il limite di 30000 caratteri. Ah bene, suppongo che sia abbastanza buono.