def zzx_hensel_lift(p, f, f_list, l): """Multifactor Hensel lifting. Given a prime p, polynomial f over Z[x] such that lc(f) is a unit modulo p, monic pair-wise coprime polynomials f_i over Z[x] satisfying: f = lc(f) f_1 ... f_r (mod p) and a positive integer l, returns a list of monic polynomials F_1, F_2, ..., F_r satisfying: f = lc(f) F_1 ... F_r (mod p**l) F_i = f_i (mod p), i = 1..r For more details on the implemented algorithm refer to: [1] J. von zur Gathen, J. Gerhard, Modern Computer Algebra, First Edition, Cambridge University Press, 1999, pp. 424 """ r = len(f_list) lc = zzx_LC(f) if r == 1: F = zzx_mul_term(f, igcdex(lc, p**l)[0], 0) return [zzx_trunc(F, p**l)] m = p k = int(r // 2) d = int(ceil(log(l, 2))) g = gf_from_int_poly([lc], p) for f_i in f_list[:k]: g = gf_mul(g, gf_from_int_poly(f_i, p), p) h = gf_from_int_poly(f_list[k], p) for f_i in f_list[k + 1:]: h = gf_mul(h, gf_from_int_poly(f_i, p), p) s, t, _ = gf_gcdex(g, h, p) g = gf_to_int_poly(g, p) h = gf_to_int_poly(h, p) s = gf_to_int_poly(s, p) t = gf_to_int_poly(t, p) for _ in range(1, d + 1): (g, h, s, t), m = zzx_hensel_step(m, f, g, h, s, t), m**2 return zzx_hensel_lift(p, g, f_list[:k], l) \ + zzx_hensel_lift(p, h, f_list[k:], l)
def zzx_hensel_lift(p, f, f_list, l): """Multifactor Hensel lifting. Given a prime p, polynomial f over Z[x] such that lc(f) is a unit modulo p, monic pair-wise coprime polynomials f_i over Z[x] satisfying: f = lc(f) f_1 ... f_r (mod p) and a positive integer l, returns a list of monic polynomials F_1, F_2, ..., F_r satisfying: f = lc(f) F_1 ... F_r (mod p**l) F_i = f_i (mod p), i = 1..r For more details on the implemented algorithm refer to: [1] J. von zur Gathen, J. Gerhard, Modern Computer Algebra, First Edition, Cambridge University Press, 1999, pp. 424 """ r = len(f_list) lc = zzx_LC(f) if r == 1: F = zzx_mul_term(f, igcdex(lc, p**l)[0], 0) return [ zzx_trunc(F, p**l) ] m = p k = int(r // 2) d = int(ceil(log(l, 2))) g = gf_from_int_poly([lc], p) for f_i in f_list[:k]: g = gf_mul(g, gf_from_int_poly(f_i, p), p) h = gf_from_int_poly(f_list[k], p) for f_i in f_list[k+1:]: h = gf_mul(h, gf_from_int_poly(f_i, p), p) s, t, _ = gf_gcdex(g, h, p) g = gf_to_int_poly(g, p) h = gf_to_int_poly(h, p) s = gf_to_int_poly(s, p) t = gf_to_int_poly(t, p) for _ in range(1, d+1): (g, h, s, t), m = zzx_hensel_step(m, f, g, h, s, t), m**2 return zzx_hensel_lift(p, g, f_list[:k], l) \ + zzx_hensel_lift(p, h, f_list[k:], l)
def test_gf_from_to_int_poly(): assert gf_from_int_poly([1,0,7,2,20], 5) == [1,0,2,2,0] assert gf_to_int_poly([1,0,4,2,3], 5) == [1,0,-1,2,-2]
def zzx_zassenhaus(f): """Factor square-free polynomials over Z[x]. """ n = zzx_degree(f) if n == 1: return [f] A = zzx_max_norm(f) b = zzx_LC(f) B = abs(int(sqrt(n + 1) * 2**n * A * b)) C = (n + 1)**(2 * n) * A**(2 * n - 1) gamma = int(ceil(2 * log(C, 2))) prime_max = int(2 * gamma * log(gamma)) for p in xrange(3, prime_max + 1): if not isprime(p) or b % p == 0: continue F = gf_from_int_poly(f, p) if gf_sqf_p(F, p): break l = int(ceil(log(2 * B + 1, p))) modular = [] for ff in gf_factor_sqf(F, p)[1]: modular.append(gf_to_int_poly(ff, p)) g = zzx_hensel_lift(p, f, modular, l) T = set(range(len(g))) factors, s = [], 1 while 2 * s <= len(T): for S in subsets(T, s): G, H = [b], [b] S = set(S) for i in S: G = zzx_mul(G, g[i]) for i in T - S: H = zzx_mul(H, g[i]) G = zzx_trunc(G, p**l) H = zzx_trunc(H, p**l) G_norm = zzx_l1_norm(G) H_norm = zzx_l1_norm(H) if G_norm * H_norm <= B: T = T - S G = zzx_primitive(G)[1] f = zzx_primitive(H)[1] factors.append(G) b = zzx_LC(f) break else: s += 1 return factors + [f]
def zzx_mod_gcd(f, g, **flags): """Modular small primes polynomial GCD over Z[x]. Given univariate polynomials f and g over Z[x], returns their GCD and cofactors, i.e. polynomials h, cff and cfg such that: h = gcd(f, g), cff = quo(f, h) and cfg = quo(g, h) The algorithm uses modular small primes approach. It works by computing several GF(p)[x] GCDs for a set of randomly chosen primes and uses Chinese Remainder Theorem to recover the GCD over Z[x] from its images. The algorithm is probabilistic which means it never fails, however its running time depends on the number of unlucky primes chosen for computing GF(p)[x] images. For more details on the implemented algorithm refer to: [1] J. von zur Gathen, J. Gerhard, Modern Computer Algebra, First Edition, Cambridge University Press, 1999, pp. 158 """ n = zzx_degree(f) m = zzx_degree(g) cf = zzx_content(f) cg = zzx_content(g) h = igcd(cf, cg) f = [c // h for c in f] g = [c // h for c in g] if n <= 0 or m <= 0: return zzx_strip([h]), f, g else: gcd = h A = max(zzx_abs(f) + zzx_abs(g)) b = igcd(zzx_LC(f), zzx_LC(g)) B = int(ceil(2**n * A * b * sqrt(n + 1))) k = int(ceil(2 * b * log((n + 1)**n * A**(2 * n), 2))) l = int(ceil(log(2 * B + 1, 2))) prime_max = max(int(ceil(2 * k * log(k))), 51) while True: while True: primes = set([]) unlucky = set([]) ff, gg, hh = {}, {}, {} while len(primes) < l: p = randprime(3, prime_max + 1) if (p in primes) and (b % p == 0): continue F = gf_from_int_poly(f, p) G = gf_from_int_poly(g, p) H = gf_gcd(F, G, p) primes.add(p) ff[p] = F gg[p] = G hh[p] = H e = min([gf_degree(h) for h in hh.itervalues()]) for p in set(primes): if gf_degree(hh[p]) != e: primes.remove(p) unlucky.add(p) del ff[p] del gg[p] del hh[p] if len(primes) < l // 2: continue while len(primes) < l: p = randprime(3, prime_max + 1) if (p in primes) or (p in unlucky) or (b % p == 0): continue F = gf_from_int_poly(f, p) G = gf_from_int_poly(g, p) H = gf_gcd(F, G, p) if gf_degree(H) != e: unlucky.add(p) else: primes.add(p) ff[p] = F gg[p] = G hh[p] = H break fff, ggg = {}, {} for p in primes: fff[p] = gf_quo(ff[p], hh[p], p) ggg[p] = gf_quo(gg[p], hh[p], p) F, G, H = [], [], [] crt_mm, crt_e, crt_s = crt1(primes) for i in xrange(0, e + 1): C = [b * zzx_nth(hh[p], i) for p in primes] c = crt2(primes, C, crt_mm, crt_e, crt_s, True) H.insert(0, c) H = zzx_strip(H) for i in xrange(0, zzx_degree(f) - e + 1): C = [zzx_nth(fff[p], i) for p in primes] c = crt2(primes, C, crt_mm, crt_e, crt_s, True) F.insert(0, c) for i in xrange(0, zzx_degree(g) - e + 1): C = [zzx_nth(ggg[p], i) for p in primes] c = crt2(primes, C, crt_mm, crt_e, crt_s, True) G.insert(0, c) H_norm = zzx_l1_norm(H) F_norm = zzx_l1_norm(F) G_norm = zzx_l1_norm(G) if H_norm * F_norm <= B and H_norm * G_norm <= B: break return zzx_mul_term(H, gcd, 0), F, G
def zzx_zassenhaus(f): """Factor square-free polynomials over Z[x]. """ n = zzx_degree(f) if n == 1: return [f] A = zzx_max_norm(f) b = zzx_LC(f) B = abs(int(sqrt(n+1)*2**n*A*b)) C = (n+1)**(2*n)*A**(2*n-1) gamma = int(ceil(2*log(C, 2))) prime_max = int(2*gamma*log(gamma)) for p in xrange(3, prime_max+1): if not isprime(p) or b % p == 0: continue F = gf_from_int_poly(f, p) if gf_sqf_p(F, p): break l = int(ceil(log(2*B + 1, p))) modular = [] for ff in gf_factor_sqf(F, p)[1]: modular.append(gf_to_int_poly(ff, p)) g = zzx_hensel_lift(p, f, modular, l) T = set(range(len(g))) factors, s = [], 1 while 2*s <= len(T): for S in subsets(T, s): G, H = [b], [b] S = set(S) for i in S: G = zzx_mul(G, g[i]) for i in T-S: H = zzx_mul(H, g[i]) G = zzx_trunc(G, p**l) H = zzx_trunc(H, p**l) G_norm = zzx_l1_norm(G) H_norm = zzx_l1_norm(H) if G_norm*H_norm <= B: T = T - S G = zzx_primitive(G)[1] f = zzx_primitive(H)[1] factors.append(G) b = zzx_LC(f) break else: s += 1 return factors + [f]
def zzx_mod_gcd(f, g, **flags): """Modular small primes polynomial GCD over Z[x]. Given univariate polynomials f and g over Z[x], returns their GCD and cofactors, i.e. polynomials h, cff and cfg such that: h = gcd(f, g), cff = quo(f, h) and cfg = quo(g, h) The algorithm uses modular small primes approach. It works by computing several GF(p)[x] GCDs for a set of randomly chosen primes and uses Chinese Remainder Theorem to recover the GCD over Z[x] from its images. The algorithm is probabilistic which means it never fails, however its running time depends on the number of unlucky primes chosen for computing GF(p)[x] images. For more details on the implemented algorithm refer to: [1] J. von zur Gathen, J. Gerhard, Modern Computer Algebra, First Edition, Cambridge University Press, 1999, pp. 158 """ n = zzx_degree(f) m = zzx_degree(g) cf = zzx_content(f) cg = zzx_content(g) h = igcd(cf, cg) f = [ c // h for c in f ] g = [ c // h for c in g ] if n <= 0 or m <= 0: return zzx_strip([h]), f, g else: gcd = h A = max(zzx_abs(f) + zzx_abs(g)) b = igcd(zzx_LC(f), zzx_LC(g)) B = int(ceil(2**n*A*b*sqrt(n + 1))) k = int(ceil(2*b*log((n + 1)**n*A**(2*n), 2))) l = int(ceil(log(2*B + 1, 2))) prime_max = max(int(ceil(2*k*log(k))), 51) while True: while True: primes = set([]) unlucky = set([]) ff, gg, hh = {}, {}, {} while len(primes) < l: p = randprime(3, prime_max+1) if (p in primes) and (b % p == 0): continue F = gf_from_int_poly(f, p) G = gf_from_int_poly(g, p) H = gf_gcd(F, G, p) primes.add(p) ff[p] = F gg[p] = G hh[p] = H e = min([ gf_degree(h) for h in hh.itervalues() ]) for p in set(primes): if gf_degree(hh[p]) != e: primes.remove(p) unlucky.add(p) del ff[p] del gg[p] del hh[p] if len(primes) < l // 2: continue while len(primes) < l: p = randprime(3, prime_max+1) if (p in primes) or (p in unlucky) or (b % p == 0): continue F = gf_from_int_poly(f, p) G = gf_from_int_poly(g, p) H = gf_gcd(F, G, p) if gf_degree(H) != e: unlucky.add(p) else: primes.add(p) ff[p] = F gg[p] = G hh[p] = H break fff, ggg = {}, {} for p in primes: fff[p] = gf_quo(ff[p], hh[p], p) ggg[p] = gf_quo(gg[p], hh[p], p) F, G, H = [], [], [] crt_mm, crt_e, crt_s = crt1(primes) for i in xrange(0, e + 1): C = [ b * zzx_nth(hh[p], i) for p in primes ] c = crt2(primes, C, crt_mm, crt_e, crt_s, True) H.insert(0, c) H = zzx_strip(H) for i in xrange(0, zzx_degree(f) - e + 1): C = [ zzx_nth(fff[p], i) for p in primes ] c = crt2(primes, C, crt_mm, crt_e, crt_s, True) F.insert(0, c) for i in xrange(0, zzx_degree(g) - e + 1): C = [ zzx_nth(ggg[p], i) for p in primes ] c = crt2(primes, C, crt_mm, crt_e, crt_s, True) G.insert(0, c) H_norm = zzx_l1_norm(H) F_norm = zzx_l1_norm(F) G_norm = zzx_l1_norm(G) if H_norm*F_norm <= B and H_norm*G_norm <= B: break return zzx_mul_term(H, gcd, 0), F, G