"""Univariate and multivariate polynomials with coefficients in the integer ring. """ from sympy.polys.galoispolys import ( gf_from_int_poly, gf_to_int_poly, gf_degree, gf_from_dict, gf_lshift, gf_add_mul, gf_mul, gf_div, gf_quo, gf_rem, gf_gcd, gf_gcdex, gf_sqf_p, gf_factor_sqf) from sympy.ntheory import randprime, nextprime, isprime, factorint from sympy.ntheory.modular import crt1, crt2 from sympy.utilities import any, all, subsets from sympy.core.numbers import Integer from math import floor, ceil, log, sqrt from random import randint from sympy.core.numbers import igcd, igcdex from sympy.mpmath.libmpf import isqrt INT_TYPE = int INT_ZERO = 0 INT_ONE = 1 from copy import deepcopy def factorial(m): k = m while m > 1: m -= 1 k *= m return k class ExactQuotientFailed(Exception): pass class HeuristicGCDFailed(Exception): pass class ExtraneousFactors(Exception): pass def poly_LC(f): """Returns leading coefficient of f. """ if not f: return INT_ZERO else: return f[0] def poly_TC(f): """Returns trailing coefficient of f. """ if not f: return INT_ZERO else: return f[-1] def poly_nth(f, n): """Returns n-th coefficient of f. """ if n < 0 or n > len(f)-1: raise IndexError else: return f[zzx_degree(f)-n] def poly_level(f): """Return the number of nested lists in f. """ if poly_univariate_p(f): return 1 else: return 1 + poly_level(poly_LC(f)) def poly_univariate_p(f): """Returns True if f is univariate. """ if not f: return True else: return type(f[0]) is not list def zzx_degree(f): """Returns leading degree of f in Z[x]. """ return len(f) - 1 def zzX_degree(f): """Returns leading degree of f in x_1 in Z[X]. """ if zzX_zero_p(f): return -1 else: return len(f) - 1 def zzX_degree_for(f, k): """Returns leading degree of f in x_k in Z[X]. """ if k < 0: k += poly_level(f) + 1 if k == 1: return zzX_degree(f) def rec_degree(g, l): if l == k: return zzX_degree(g) else: return max([ rec_degree(coeff, l+1) for coeff in g ]) return rec_degree(f, 1) def zzX_degree_all(f): """Returns total degree of f in Z[X]. """ degs = [-1]*poly_level(f) def rec_degree(g, l): degs[l-1] = max(degs[l-1], zzX_degree(g)) if not poly_univariate_p(g): for coeff in g: rec_degree(coeff, l+1) rec_degree(f, 1) return tuple(degs) def zzx_strip(f): """Remove leading zeros from f in Z[x]. """ if not f or f[0]: return f k = 0 for coeff in f: if coeff: break else: k += 1 return f[k:] def zzX_strip(f): """Remove leading zeros from f in Z[X]. """ if poly_univariate_p(f): return zzx_strip(f) if zzX_zero_p(f): return f k = 0 for coeff in f: if not zzX_zero_p(coeff): break else: k += 1 if k == len(f): return zzX_zero_of(f) else: return f[k:] def zzX_valid_p(f): """Returns True if f is a valid polynomial in Z[x]. """ levels = [] def rec_valid(g, l): if poly_univariate_p(g): levels.append(l) return zzx_strip(g) == g else: return zzX_strip(g) == g and \ all([ rec_valid(h, l+1) for h in g ]) return rec_valid(f, 1) and len(set(levels)) == 1 def zzX_zz_LC(f): """Returns integer leading coefficient. """ if poly_univariate_p(f): return poly_LC(f) else: return zzX_zz_LC(poly_LC(f)) def zzX_zz_TC(f): """Returns integer trailing coefficient. """ if poly_univariate_p(f): return poly_TC(f) else: return zzX_zz_TC(poly_TC(f)) def zzX_zero(l): """Returns multivariate zero. """ if not l: return INT_ZERO elif l == 1: return [] else: return [zzX_zero(l-1)] def zzX_zero_of(f, d=0): """Returns multivariate zero of f. """ return zzX_zero(poly_level(f)-d) def zzX_const(l, c): """Returns multivariate constant. """ if not c: return zzX_zero(l) else: if not l: return INT_TYPE(c) elif l == 1: return [INT_TYPE(c)] else: return [zzX_const(l-1, c)] def zzX_const_of(f, c, d=0): """Returns multivariate constant of f. """ return zzX_const(poly_level(f)-d, c) def zzX_zeros_of(f, k, d=0): """Returns a list of multivariate zeros of f. """ if poly_univariate_p(f): return [INT_ZERO]*k l = poly_level(f)-d if not k: return [] else: return [ zzX_zero(l) for i in xrange(k) ] def zzX_consts_of(f, c, k, d=0): """Returns a list of multivariate constants of f. """ if poly_univariate_p(f): return [INT_TYPE(c)]*k l = poly_level(f)-d if not k: return [] else: return [ zzX_const(l, c) for i in xrange(k) ] def zzX_zero_p(f): """Returns True if f is zero in Z[X]. """ if poly_univariate_p(f): return not f else: if len(f) == 1: return zzX_zero_p(f[0]) else: return False def zzx_one_p(f): """Returns True if f is one in Z[x]. """ return f == [INT_ONE] def zzX_one_p(f): """Returns True if f is one in Z[X]. """ if poly_univariate_p(f): return zzx_one_p(f) else: if len(f) == 1: return zzX_one_p(f[0]) else: return False def zzX_value(l, f): """Returns multivariate value nested l-levels. """ if type(f) is not list: return zzX_const(l, f) else: if not l: return f else: return [zzX_value(l-1, f)] def zzX_lift(l, f): """Returns multivariate polynomial lifted l-levels. """ if poly_univariate_p(f): if not f: return zzX_zero(l+1) else: return [ zzX_const(l, c) for c in f ] else: return [ zzX_lift(l, c) for c in f ] def zzx_from_dict(f): """Create Z[x] polynomial from a dict. """ if not f: return [] n, h = max(f.iterkeys()), [] for k in xrange(n, -1, -1): h.append(INT_TYPE(int(f.get(k, 0)))) return zzx_strip(h) def zzX_from_dict(f, l): """Create Z[X] polynomial from a dict. """ if l == 1: return zzx_from_dict(f) elif not f: return zzX_zero(l) coeffs = {} for monom, coeff in f.iteritems(): head, tail = monom[0], monom[1:] if len(tail) == 1: tail = tail[0] if head in coeffs: coeffs[head][tail] = INT_TYPE(int(coeff)) else: coeffs[head] = { tail : INT_TYPE(int(coeff)) } n, h = max(coeffs.iterkeys()), [] for k in xrange(n, -1, -1): coeff = coeffs.get(k) if coeff is not None: h.append(zzX_from_dict(coeff, l-1)) else: h.append(zzX_zero(l-1)) return zzX_strip(h) def zzx_to_dict(f): """Convert Z[x] polynomial to a dict. """ n, result = zzx_degree(f), {} for i in xrange(0, n+1): if f[n-i]: result[i] = f[n-i] return result def zzX_to_dict(f): """Convert Z[X] polynomial to a dict. """ if poly_univariate_p(f): return zzx_to_dict(f) n, result = zzX_degree(f), {} for i in xrange(0, n+1): h = zzX_to_dict(f[n-i]) for exp, coeff in h.iteritems(): if type(exp) is not tuple: exp = (exp,) result[(i,)+exp] = coeff return result def zzx_from_poly(f): """Convert Poly instance to a recursive dense polynomial in Z[x]. """ return zzx_from_dict(dict(zip([ m for (m,) in f.monoms ], f.coeffs))) def zzX_from_poly(f): """Convert Poly instance to a recursive dense polynomial in Z[X]. """ if f.is_univariate: return zzx_from_poly(f) else: return zzX_from_dict(dict(zip(f.monoms, f.coeffs)), len(f.symbols)) def zzx_to_poly(f, *symbols): """Convert recursive dense polynomial to a Poly in Z[x]. """ from sympy.polys import Poly terms = {} for monom, coeff in zzx_to_dict(f).iteritems(): terms[(monom,)] = Integer(int(coeff)) return Poly(terms, *symbols) def zzX_to_poly(f, *symbols): """Convert recursive dense polynomial to a Poly in Z[X]. """ from sympy.polys import Poly terms = {} for monom, coeff in zzX_to_dict(f).iteritems(): terms[monom] = Integer(int(coeff)) return Poly(terms, *symbols) def zzX_swap(f, i=1, j=2): """Transform Z[..x_i..x_j..] to Z[..x_j..x_i..]. """ l = poly_level(f) if i < 1 or j < 1 or i > l or j > l: raise ValueError("1 <= i < j <= lev(f) expected") elif i == j: return f else: i, j = i-1, j-1 F, H = zzX_to_dict(f), {} for exp, coeff in F.iteritems(): H[exp[:i] + (exp[j],) + exp[i+1:j] + (exp[i],) + exp[j+1:]] = coeff return zzX_from_dict(H, l) def zzx_abs(f): """Make all coefficients positive in Z[x]. """ return [ abs(coeff) for coeff in f ] def zzX_abs(f): """Make all coefficients positive in Z[X]. """ if poly_univariate_p(f): return zzx_abs(f) else: return [ zzX_abs(coeff) for coeff in f ] def zzx_neg(f): """Negate a polynomial in Z[x]. """ return [ -coeff for coeff in f ] def zzX_neg(f): """Negate a polynomial in Z[X]. """ if poly_univariate_p(f): return zzx_neg(f) else: return [ zzX_neg(coeff) for coeff in f ] def zzx_add_term(f, c, k=0): """Add c*x**k to f in Z[x]. """ if not c: return f n = len(f) m = n-k-1 if k == n-1: return zzx_strip([f[0]+c] + f[1:]) else: if k >= n: return [c] + [INT_ZERO]*(k-n) + f else: return f[:m] + [f[m]+c] + f[m+1:] def zzX_add_term(f, c, k=0): """Add c*x**k to f in Z[X]. """ if poly_univariate_p(f): return zzx_add_term(f, c, k) if zzX_zero_p(c): return f n = len(f) m = n-k-1 if k == n-1: return zzX_strip([zzX_add(f[0], c)] + f[1:]) else: if k >= n: return [c] + zzX_zeros_of(f, k-n, 1) + f else: return f[:m] + [zzX_add(f[m], c)] + f[m+1:] def zzx_sub_term(f, c, k=0): """Subtract c*x**k from f in Z[x]. """ if not c: return f n = len(f) m = n-k-1 if k == n-1: return zzx_strip([f[0]-c] + f[1:]) else: if k >= n: return [-c] + [INT_ZERO]*(k-n) + f else: return f[:m] + [f[m]-c] + f[m+1:] def zzX_sub_term(f, c, k=0): """Subtract c*x**k from f in Z[X]. """ return zzX_add_term(f, zzX_neg(c), k) def zzx_mul_term(f, c, k): """Multiply f by c*x**k in Z[x]. """ if not c or not f: return [] else: return [ c * coeff for coeff in f ] + [INT_ZERO]*k def zzX_mul_term(f, c, k): """Multiply f by c*x**k in Z[X]. """ if poly_univariate_p(f): return zzx_mul_term(f, c, k) elif zzX_zero_p(f): return f elif zzX_zero_p(c): return zzX_zero_of(f) else: return [ zzX_mul(c, coeff) for coeff in f ] + zzX_zeros_of(f, k, 1) def zzx_mul_const(f, c): """Multiply f by constant value in Z[x]. """ if not c or not f: return [] else: return [ c * coeff for coeff in f ] def zzX_mul_const(f, c): """Multiply f by constant value in Z[X]. """ if poly_univariate_p(f): return zzx_mul_const(f, c) else: return [ zzX_mul_const(coeff, c) for coeff in f ] def zzx_quo_const(f, c): """Exact quotient by a constant in Z[x]. """ if not c: raise ZeroDivisionError('polynomial division') elif not f: return f else: h = [] for coeff in f: if coeff % c: raise ExactQuotientFailed('%s does not divide %s' % (c, coeff)) else: h.append(coeff // c) return h def zzX_quo_const(f, c): """Exact quotient by a constant in Z[X]. """ if poly_univariate_p(f): return zzx_quo_const(f, c) else: return [ zzX_quo_const(coeff, c) for coeff in f ] def zzx_compose_term(f, k): """Map y -> x**k in a polynomial in Z[x]. """ if k <= 0: raise ValueError("'k' must be positive, got %s" % k) if k == 1 or not f: return f result = [f[0]] for coeff in f[1:]: result.extend([0]*(k-1)) result.append(coeff) return result def zzX_compose_term(f, K): """Map y_i -> x_i**k_i in a polynomial in Z[X]. """ def rec_compose(g, l): if poly_univariate_p(g): return zzx_compose_term(g, K[l]) if K[l] <= 0: raise ValueError("All 'K[i]' must be positive, got %s" % K[l]) g = [ rec_compose(c, l+1) for c in g ] result, L = [g[0]], poly_level(g) - 1 for coeff in g[1:]: for i in xrange(1, K[l]): result.append(zzX_zero(L)) result.append(coeff) return result if all([ k == 1 for k in K ]): return f else: return rec_compose(f, 0) def zzx_reduce(f): """Map x**k -> y in a polynomial in Z[x]. """ if zzx_degree(f) <= 0: return 1, f g = INT_ZERO for i in xrange(len(f)): if not f[-i-1]: continue g = igcd(g, i) if g == 1: return 1, f return g, f[::g] def zzX_reduce(f): """Map x_i**k_i -> y_i in a polynomial in Z[X]. """ if zzX_zero_p(f): return (1,)*poly_level(f), f F, H = zzX_to_dict(f), {} def ilgcd(M): g = 0 for m in M: g = igcd(g, m) if g == 1: break return g or 1 M = tuple(map(lambda *row: ilgcd(row), *F.keys())) if all([ b == 1 for b in M ]): return M, f for m, coeff in F.iteritems(): N = [ a // b for a, b in zip(m, M) ] H[tuple(N)] = coeff return M, zzX_from_dict(H, len(M)) def zzx_multi_reduce(*polys): """Map x**k -> y in a set of polynomials in Z[x]. """ G = INT_ZERO for p in polys: if zzx_degree(p) <= 0: return 1, polys g = INT_ZERO for i in xrange(len(p)): if not p[-i-1]: continue g = igcd(g, i) if g == 1: return 1, polys G = igcd(G, g) if G == 1: return 1, polys return G, tuple([ p[::G] for p in polys ]) def zzX_multi_reduce(*polys): """Map x_i**k_i -> y_i in a set of polynomials in Z[X]. """ def ilgcd(M): g = 0 for m in M: g = igcd(g, m) if g == 1: break return g or 1 l = poly_level(polys[0]) if l == 1: M, H = zzx_multi_reduce(*polys) return (M,), H F, M, H = [], [], [] for p in polys: f = zzX_to_dict(p) if zzX_zero_p(p): m = (0,)*l else: m = map(lambda *row: ilgcd(row), *f.keys()) F.append(f) M.append(m) M = tuple(map(lambda *row: ilgcd(row), *M)) if all([ b == 1 for b in M ]): return M, polys for f in F: h = {} for m, coeff in f.iteritems(): N = [ a // b for a, b in zip(m, M) ] h[tuple(N)] = coeff H.append(zzX_from_dict(h, len(m))) return M, tuple(H) def zzx_add(f, g): """Add polynomials in Z[x]. """ if not f: return g if not g: return f df = zzx_degree(f) dg = zzx_degree(g) if df == dg: return zzx_strip([ a + b for a, b in zip(f, g) ]) else: k = abs(df - dg) if df > dg: h, f = f[:k], f[k:] else: h, g = g[:k], g[k:] return h + [ a + b for a, b in zip(f, g) ] def zzX_add(f, g): """Add polynomials in Z[X]. """ if poly_univariate_p(f): return zzx_add(f, g) if zzX_zero_p(f): return g if zzX_zero_p(g): return f df = zzX_degree(f) dg = zzX_degree(g) if df == dg: return zzX_strip([ zzX_add(a, b) for a, b in zip(f, g) ]) else: k = abs(df - dg) if df > dg: h, f = f[:k], f[k:] else: h, g = g[:k], g[k:] return h + [ zzX_add(a, b) for a, b in zip(f, g) ] def zzx_sub(f, g): """Subtract polynomials in Z[x]. """ if not g: return f if not f: return zzx_neg(g) df = zzx_degree(f) dg = zzx_degree(g) if df == dg: return zzx_strip([ a - b for a, b in zip(f, g) ]) else: k = abs(df - dg) if df > dg: h, f = f[:k], f[k:] else: h, g = zzx_neg(g[:k]), g[k:] return h + [ a - b for a, b in zip(f, g) ] def zzX_sub(f, g): """Subtract polynomials in Z[x]. """ if poly_univariate_p(f): return zzx_sub(f, g) if zzX_zero_p(g): return f if zzX_zero_p(f): return zzX_neg(g) df = zzX_degree(f) dg = zzX_degree(g) if df == dg: return zzX_strip([ zzX_sub(a, b) for a, b in zip(f, g) ]) else: k = abs(df - dg) if df > dg: h, f = f[:k], f[k:] else: h, g = zzX_neg(g[:k]), g[k:] return h + [ zzX_sub(a, b) for a, b in zip(f, g) ] def zzx_add_mul(f, g, h): """Returns f + g*h where f, g, h in Z[x]. """ return zzx_add(f, zzx_mul(g, h)) def zzX_add_mul(f, g, h): """Returns f + g*h where f, g, h in Z[X]. """ return zzX_add(f, zzX_mul(g, h)) def zzx_sub_mul(f, g, h): """Returns f - g*h where f, g, h in Z[x]. """ return zzx_sub(f, zzx_mul(g, h)) def zzX_sub_mul(f, g, h): """Returns f - g*h where f, g, h in Z[X]. """ return zzX_sub(f, zzX_mul(g, h)) def zzx_mul(f, g): """Multiply polynomials in Z[x]. """ if f == g: return zzx_sqr(f) if not (f and g): return [] df = zzx_degree(f) dg = zzx_degree(g) h = [] for i in xrange(0, df+dg+1): coeff = 0 for j in xrange(max(0, i-dg), min(df, i)+1): coeff += f[j]*g[i-j] h.append(coeff) return h def zzX_mul(f, g): """Multiply polynomials in Z[X]. """ if poly_univariate_p(f): return zzx_mul(f, g) if f == g: return zzX_sqr(f) if zzX_zero_p(f): return f if zzX_zero_p(g): return g df = zzX_degree(f) dg = zzX_degree(g) h, l = [], poly_level(f)-1 for i in xrange(0, df+dg+1): coeff = zzX_zero(l) for j in xrange(max(0, i-dg), min(df, i)+1): coeff = zzX_add(coeff, zzX_mul(f[j], g[i-j])) h.append(coeff) return h def zzx_sqr(f): """Square polynomials in Z[x]. """ df, h = zzx_degree(f), [] for i in xrange(0, 2*df+1): coeff = INT_ZERO jmin = max(0, i-df) jmax = min(i, df) n = jmax - jmin + 1 jmax = jmin + n // 2 - 1 for j in xrange(jmin, jmax+1): coeff += f[j]*f[i-j] coeff += coeff if n & 1: elem = f[jmax+1] coeff += elem**2 h.append(coeff) return h def zzX_sqr(f): """Square polynomials in Z[X]. """ if poly_univariate_p(f): return zzx_sqr(f) if zzX_zero_p(f): return f df = zzX_degree(f) l = poly_level(f)-1 h = [] for i in xrange(0, 2*df+1): coeff = zzX_zero(l) jmin = max(0, i-df) jmax = min(i, df) n = jmax - jmin + 1 jmax = jmin + n // 2 - 1 for j in xrange(jmin, jmax+1): coeff = zzX_add(coeff, zzX_mul(f[j], f[i-j])) coeff = zzX_mul_const(coeff, 2) if n & 1: elem = zzX_sqr(f[jmax+1]) coeff = zzX_add(coeff, elem) h.append(coeff) return h def zzx_pow(f, n): """Raise f to the n-th power in Z[x]. """ if not n: return [INT_ONE] if n == 1 or f == [] or f == [1]: return f g = [INT_ONE] while True: n, m = n//2, n if m & 1: g = zzx_mul(g, f) if n == 0: break f = zzx_sqr(f) return g def zzX_pow(f, n): """Raise f to the n-th power in Z[X]. """ if poly_univariate_p(f): return zzx_pow(f, n) if not n: return zzX_const_of(f, 1) if n == 1 or zzX_zero_p(f) or zzX_one_p(f): return f g = zzX_const_of(f, 1) while True: n, m = n//2, n if m & 1: g = zzX_mul(g, f) if n == 0: break f = zzX_sqr(f) return g def zzx_expand(*polys): """Multiply together several polynomials in Z[x]. """ f = polys[0] for g in polys[1:]: f = zzx_mul(f, g) return f def zzX_expand(*polys): """Multiply together several polynomials in Z[X]. """ f = polys[0] for g in polys[1:]: f = zzX_mul(f, g) return f def zzx_div(f, g): """Returns quotient and remainder in Z[x]. """ df = zzx_degree(f) dg = zzx_degree(g) if not g: raise ZeroDivisionError("polynomial division") elif df < dg: return [], f q, r = [], f while True: dr = zzx_degree(r) if dr < dg: break lc_r = poly_LC(r) lc_g = poly_LC(g) if lc_r % lc_g != 0: break c, k = lc_r // lc_g, dr - dg q = zzx_add_term(q, c, k) h = zzx_mul_term(g, c, k) r = zzx_sub(r, h) return q, r def zzX_div(f, g): """Returns quotient and remainder in Z[X]. """ if poly_univariate_p(f): return zzx_div(f, g) df = zzX_degree(f) dg = zzX_degree(g) if dg < 0: raise ZeroDivisionError("polynomial division") q, r = zzX_zero_of(f), f if df < dg: return q, r while True: dr = zzX_degree(r) if dr < dg: break lc_r = poly_LC(r) lc_g = poly_LC(g) c, R = zzX_div(lc_r, lc_g) if not zzX_zero_p(R): break k = dr - dg q = zzX_add_term(q, c, k) h = zzX_mul_term(g, c, k) r = zzX_sub(r, h) return q, r def zzx_quo(f, g): """Returns polynomial remainder in Z[x]. """ return zzx_div(f, g)[0] def zzX_quo(f, g): """Returns polynomial remainder in Z[X]. """ return zzX_div(f, g)[0] def zzx_rem(f, g): """Returns polynomial remainder in Z[x]. """ return zzx_div(f, g)[1] def zzX_rem(f, g): """Returns polynomial remainder in Z[X]. """ return zzX_div(f, g)[1] def zzx_max_norm(f): """Returns maximum norm of a polynomial in Z[x]. """ if not f: return INT_ZERO else: return max(zzx_abs(f)) def zzX_max_norm(f): """Returns maximum norm of a polynomial in Z[X]. """ if poly_univariate_p(f): return zzx_max_norm(f) else: return max([ zzX_max_norm(coeff) for coeff in f ]) def zzx_l1_norm(f): """Returns l1 norm of a polynomial in Z[x]. """ if not f: return INT_ZERO else: return sum(zzx_abs(f)) def zzX_l1_norm(f): """Returns l1 norm of a polynomial in Z[X]. """ if poly_univariate_p(f): return zzx_l1_norm(f) else: return sum([ zzX_l1_norm(coeff) for coeff in f ]) def zzx_mignotte_bound(f): """Mignotte bound for univariate polynomials. """ a = zzx_max_norm(f) b = abs(poly_LC(f)) n = zzx_degree(f) return INT_TYPE(isqrt(n+1))*2**n*a*b def zzX_mignotte_bound(f): """Mignotte bound for multivariate polynomials. """ a = zzX_max_norm(f) b = abs(zzX_zz_LC(f)) n = sum(zzX_degree_all(f)) return INT_TYPE(isqrt(n+1))*2**n*a*b def zzx_diff(f, m=1): """m-th order derivative of a polynomial in Z[x]. """ n, deriv = zzx_degree(f), [] n = zzX_degree(f) if n < m: return [] deriv, c = [], INT_ONE for i in xrange(0, m): c, n = c*n, n-1 for coeff in f[:-m]: deriv.append(coeff*c) c, n = n*(c//(n+m)), n-1 return deriv def zzX_diff(f, m=1): """m-th order derivative in x_0 of a polynomial in Z[X]. """ if poly_univariate_p(f): return zzx_diff(f, m) n = zzX_degree(f) if n < m: return zzX_zero_of(f) deriv, c = [], INT_ONE for i in xrange(0, m): c, n = c*n, n-1 for coeff in f[:-m]: h = zzX_mul_const(coeff, c) c, n = n*(c//(n+m)), n-1 deriv.append(h) return deriv def zzX_diff_for(f, k, m=1): """m-th order derivative in x_k of a polynomial in Z[X]. """ if k < 0: k += poly_level(f) + 1 if k == 1: return zzX_diff(f, m) def rec_diff(g, l): if l == k: return zzX_diff(g, m) else: return zzX_strip([ rec_diff(coeff, l+1) for coeff in g ]) return rec_diff(f, 1) def zzx_eval(f, x): """Evaluate f(x) in Z[x] using Horner scheme. """ result = INT_ZERO if not x: return poly_TC(f) for a in f: result *= x result += a return result def zzX_eval(f, x): """Evaluate f in Z[X] in variable x_1, using Horner scheme. """ if hasattr(x, '__iter__'): return zzX_eval_list(f, x) if poly_univariate_p(f): return zzx_eval(f, x) if not x: return poly_TC(f) result = poly_LC(f) for coeff in f[1:]: result = zzX_mul_const(result, x) result = zzX_add(result, coeff) return result def zzX_eval_for(f, k, x): """Evaluate f in Z[X] in variable x_k, using Horner scheme. """ if k < 0: k += poly_level(f) + 1 if k == 1: return zzX_eval(f, x) def rec_eval(g, l): if l == k: return zzX_eval(g, x) else: return zzX_strip([ rec_eval(coeff, l+1) for coeff in g ]) return rec_eval(f, 1) def zzX_eval_list(f, A): """Evaluate f(x_j,...,x_n) polynomial in Z[X]. """ def rec_eval(g, l, L): if l == L: return zzx_eval(g, A[-1]) else: h = [ rec_eval(h, l+1, L) for h in g ] if l <= L - len(A): return h else: return zzx_eval(h, A[-L+l-1]) if not A: return f L = poly_level(f) if zzX_zero_p(f): return zzX_zero(L - len(A)) e = rec_eval(f, 1, L) if L == len(A): return e else: return zzX_strip(e) def zzX_diff_eval(f, k, m, x): """Differentiate and evaluate f in Z[X] in variable x_k. """ if k < 0: k += poly_level(f) + 1 if k == 1: return zzX_eval(zzX_diff(f, m), x) def rec_eval(g, l): if l == k: return zzX_eval(zzX_diff(g, m), x) else: return zzX_strip([ rec_eval(coeff, l+1) for coeff in g ]) return rec_eval(f, 1) def zzx_trunc(f, p): """Reduce Z[x] polynomial modulo integer p. """ g = [] for coeff in f: coeff %= p if coeff > p // 2: g.append(coeff - p) else: g.append(coeff) return zzx_strip(g) def zzX_trunc(f, p): """Reduce Z[X] polynomial modulo polynomial p. """ return zzX_strip([ zzX_rem(g, p) for g in f ]) def zzX_zz_trunc(f, p): """Reduce Z[X] polynomial modulo integer p. """ if poly_univariate_p(f): return zzx_trunc(f, p) else: return zzX_strip([ zzX_zz_trunc(g, p) for g in f ]) def zzx_content(f): """Returns integer GCD of coefficients in Z[x]. """ cont = INT_ZERO for coeff in f: cont = igcd(cont, coeff) if cont == 1: break return cont def zzX_content(f): """Returns polynomial GCD of coefficients in Z[X]. """ cont = poly_LC(f) for g in f[1:]: cont = zzX_gcd(cont, g) if zzX_one_p(cont): break return cont def zzX_zz_content(f): """Returns integer GCD of coefficients in Z[X]. """ if poly_univariate_p(f): return zzx_content(f) cont = INT_ZERO for g in f: gc = zzX_zz_content(g) cont = igcd(cont, gc) if cont == 1: break return cont def zzx_primitive(f): """Divides all coefficients by integer content. """ cont = zzx_content(f) if cont == 1: return 1, f else: return cont, [ coeff // cont for coeff in f ] def zzX_primitive(f): """Divides all coefficients by polynomial content. """ cont = zzX_content(f) if zzX_one_p(cont): return cont, f else: return cont, [ zzX_quo(coeff, cont) for coeff in f ] def zzX_zz_primitive(f): """Divides all coefficients by integer content in Z[X]. """ cont = zzX_zz_content(f) if cont == 1: return 1, f else: return cont, zzX_quo_const(f, cont) def zzx_sqf_part(f): """Returns square-free part of a polynomial in Z[x]. """ quo = zzx_quo(f, zzx_gcd(f, zzx_diff(f))) return zzx_primitive(quo)[1] def zzX_sqf_part(f): """Returns square-free part of a polynomial in Z[X]. """ quo = zzX_quo(f, zzX_gcd(f, zzX_diff(f))) return zzX_primitive(quo)[1] def zzx_sqf_p(f): """Returns True if f is a square-free polynomial in Z[x]. """ return zzx_one_p(zzx_gcd(zzx_primitive(f)[1], zzx_diff(f))) def zzX_sqf_p(f): """Returns True if f is a square-free polynomial in Z[X]. """ return zzX_one_p(zzX_gcd(zzX_primitive(f)[1], zzX_diff(f))) def zzx_gcd(f, g, **flags): """Returns polynomial GCD in Z[x]. """ return zzx_cofactors(f, g, **flags)[0] def zzx_cofactors(f, g, **flags): """Returns polynomial GCD and its co-factors in Z[x]. """ return zzx_heu_gcd(f, g, **flags) def zzX_gcd(f, g, **flags): """Returns polynomial GCD in Z[X]. """ return zzX_cofactors(f, g, **flags)[0] def zzX_cofactors(f, g, **flags): """Returns polynomial GCD and its co-factors in Z[X]. """ if poly_univariate_p(f): return zzx_heu_gcd(f, g, **flags) if not flags.get('reduced', True): return zzX_heu_gcd(f, g, **flags) else: K, (f, g) = zzX_multi_reduce(f, g) return [ zzX_compose_term(h, K) for h in zzX_heu_gcd(f, g, **flags) ] def zzx_heu_gcd(f, g, **flags): """Heuristic 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 is purely heuristic which means it may fail to compute the GCD. This will be signaled by raising an exception. In this case you will need to switch to another GCD method. The algorithm computes the polynomial GCD by evaluating polynomials f and g at certain points and computing (fast) integer GCD of those evaluations. The polynomial GCD is recovered from the integer image by interpolation. The final step is to verify if the result is the correct GCD. This gives cofactors of input polynomials as a side effect (see zzX_cofactors). For more details on the implemented algorithm refer to: [1] Hsin-Chao Liao, R. Fateman, Evaluation of the heuristic polynomial GCD, International Symposium on Symbolic and Algebraic Computation (ISSAC), ACM Press, Montreal, Quebec, Canada, 1995, pp. 240--247 """ def interpolate(h, x): f = [] while h: g = h % x if g > x // 2: g -= x f.insert(0, g) h = (h-g) // x return f def finalize(h, cff, cfg, gcd): h = zzx_mul_const(h, gcd) return h, cff, cfg if not (f or g): return [], [], [] elif not f: return g, [], [1] elif not g: return f, [1], [] df = zzx_degree(f) dg = zzx_degree(g) cf = zzx_content(f) cg = zzx_content(g) gcd = igcd(cf, cg) f = [ c // gcd for c in f ] g = [ c // gcd for c in g ] if df == 0 or dg == 0: return [gcd], f, g f_norm = zzx_max_norm(f) g_norm = zzx_max_norm(g) B = 2*min(f_norm, g_norm) + 29 x = max(min(B, 99*INT_TYPE(isqrt(B))), 2*min(f_norm // abs(poly_LC(f)), g_norm // abs(poly_LC(g))) + 2) for i in xrange(0, 6): ff = zzx_eval(f, x) gg = zzx_eval(g, x) if ff and gg: h = igcd(ff, gg) cff = ff // h cfg = gg // h h = interpolate(h, x) h = zzx_primitive(h)[1] cff_, r = zzx_div(f, h) if not r: cfg_, r = zzx_div(g, h) if not r: return finalize(h, cff_, cfg_, gcd) cff = interpolate(cff, x) h, r = zzx_div(f, cff) if not r: cfg_, r = zzx_div(g, h) if not r: return finalize(h, cff, cfg_, gcd) cfg = interpolate(cfg, x) h, r = zzx_div(g, cfg) if not r: cff_, r = zzx_div(f, h) if not r: return finalize(h, cff_, cfg, gcd) x = INT_TYPE(2.7319*x*isqrt(isqrt(x))) raise HeuristicGCDFailed('no luck') def zzX_heu_gcd(f, g, **flags): """Heuristic polynomial GCD in Z[X]. Given univariate polynomials f and g in 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 is purely heuristic which means it may fail to compute the GCD. This will be signaled by raising an exception. In this case you will need to switch to another GCD method. The algorithm computes the polynomial GCD by evaluating polynomials f and g at certain points and computing (fast) integer GCD of those evaluations. The polynomial GCD is recovered from the integer image by interpolation. The evaluation proces reduces f and g variable by variable into a large integer. The final step is to verify if the interpolated polynomial is the correct GCD. This gives cofactors of the input polynomials as a side effect (see zzX_cofactors). For more details on the implemented algorithm refer to: [1] Hsin-Chao Liao, R. Fateman, Evaluation of the heuristic polynomial GCD, International Symposium on Symbolic and Algebraic Computation (ISSAC), ACM Press, Montreal, Quebec, Canada, 1995, pp. 240--247 """ if poly_univariate_p(f): return zzx_heu_gcd(f, g, **flags) def interpolate(h, x): f = [] while not zzX_zero_p(h): g = zzX_zz_trunc(h, x) f.insert(0, g) h = zzX_sub(h, g) h = zzX_quo_const(h, x) return f def finalize(h, cff, cfg, gcd): if zzX_zz_LC(h) > 0: h = zzX_mul_const(h, gcd) else: h = zzX_mul_const(h, -gcd) cff = zzX_neg(cff) cfg = zzX_neg(cfg) return h, cff, cfg zero_f = zzX_zero_p(f) zero_g = zzX_zero_p(g) l = poly_level(f) z = zzX_zero(l) if zero_f and zero_g: return z, z, z elif zero_f: return g, z, zzX_const(l, 1) elif zero_g: return f, zzX_const(l, 1), z df = zzX_degree(f) dg = zzX_degree(g) cf = zzX_zz_content(f) cg = zzX_zz_content(g) gcd = igcd(cf, cg) f = zzX_quo_const(f, gcd) g = zzX_quo_const(g, gcd) f_norm = zzX_max_norm(f) g_norm = zzX_max_norm(g) B = 2*min(f_norm, g_norm) + 29 x = max(min(B, 99*INT_TYPE(isqrt(B))), 2*min(f_norm // abs(zzX_zz_LC(f)), g_norm // abs(zzX_zz_LC(g))) + 2) for i in xrange(0, 6): ff = zzX_eval(f, x) gg = zzX_eval(g, x) if not (zzX_zero_p(ff) or zzX_zero_p(gg)): h, cff, cfg = zzX_heu_gcd(ff, gg, **flags) h = interpolate(h, x) h = zzX_zz_primitive(h)[1] cff_, r = zzX_div(f, h) if zzX_zero_p(r): cfg_, r = zzX_div(g, h) if zzX_zero_p(r): return finalize(h, cff_, cfg_, gcd) cff = interpolate(cff, x) h, r = zzX_div(f, cff) if zzX_zero_p(r): cfg_, r = zzX_div(g, h) if zzX_zero_p(r): return finalize(h, cff, cfg_, gcd) cfg = interpolate(cfg, x) h, r = zzX_div(g, cfg) if zzX_zero_p(r): cff_, r = zzX_div(f, h) if zzX_zero_p(r): return finalize(h, cff_, cfg, gcd) x = INT_TYPE(2.7319*x*isqrt(isqrt(x))) raise HeuristicGCDFailed('no luck') # XXX: this is completely broken 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 """ if not (f or g): return [], [], [] elif not f: return g, [], [1] elif not g: return f, [1], [] n = zzx_degree(f) m = zzx_degree(g) cf = zzx_content(f) cg = zzx_content(g) gcd = igcd(cf, cg) f = [ c // gcd for c in f ] g = [ c // gcd for c in g ] if n == 0 or m == 0: return [gcd], f, g A = max(zzx_abs(f) + zzx_abs(g)) b = igcd(poly_LC(f), poly_LC(g)) B = int(ceil(2**n*A*b*int(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) 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) 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 * poly_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 = [ poly_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 = [ poly_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_const(H, gcd), F, G def zzx_hensel_step(m, f, g, h, s, t): """One step in Hensel lifting. Given positive integer m and Z[x] polynomials f, g, h, s and t such that: [1] f == g*h (mod m) [2] s*g + t*h == 1 (mod m) [3] lc(f) not a zero divisor (mod m) [4] lc(h) == 1 [5] deg(f) == deg(g) + deg(h) [6] deg(s) < deg(h) [7] deg(t) < deg(g) returns polynomials G, H, S and T, such that: [A] f == G*H (mod m**2) [B] S*G + T**H == 1 (mod m**2) 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. 418 """ M = m**2 e = zzx_sub_mul(f, g, h) e = zzx_trunc(e, M) q, r = zzx_div(zzx_mul(s, e), h) q = zzx_trunc(q, M) r = zzx_trunc(r, M) u = zzx_add(zzx_mul(t, e), zzx_mul(q, g)) G = zzx_trunc(zzx_add(g, u), M) H = zzx_trunc(zzx_add(h, r), M) u = zzx_add(zzx_mul(s, G), zzx_mul(t, H)) b = zzx_trunc(zzx_sub(u, [1]), M) c, d = zzx_div(zzx_mul(s, b), H) c = zzx_trunc(c, M) d = zzx_trunc(d, M) u = zzx_add(zzx_mul(t, b), zzx_mul(c, G)) S = zzx_trunc(zzx_sub(s, d), M) T = zzx_trunc(zzx_sub(t, u), M) return G, H, S, T 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 = poly_LC(f) if r == 1: F = zzx_mul_const(f, igcdex(lc, p**l)[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_zassenhaus(f): """Factor primitive square-free polynomials in Z[x]. """ n = zzx_degree(f) if n == 1: return [f] A = zzx_max_norm(f) b = poly_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 = poly_LC(f) break else: s += 1 return factors + [f] def zzx_eisenstein(f): """Eisenstein's irreducibility criterion. """ lc = poly_LC(f) tc = poly_TC(f) e_fc = zzx_content(f[1:]) if not e_fc: return e_ff = factorint(e_fc) for p in e_ff.iterkeys(): if (lc % p) and (tc % p**2): return True def zzx_factor(f, **flags): """Factor (non square-free) polynomials in Z[x]. Given a univariate polynomial f in Z[x] computes its complete factorization f_1, ..., f_n into irreducibles over integers: f = content(f) f_1**k_1 ... f_n**k_n The factorization is computed by reducing the input polynomial into a primitive square-free polynomial and factoring it using Zassenhaus algorithm. Trial division is used to recover the multiplicities of factors. The result is returned as a tuple consisting of: (content(f), [(f_1, k_1), ..., (f_n, k_n)) Consider polynomial f = 2*x**4 - 2: >>> zzx_factor([2, 0, 0, 0, -2]) (2, [([1, -1], 1), ([1, 1], 1), ([1, 0, 1], 1)]) In result we got the following factorization: f = 2 (x - 1) (x + 1) (x**2 + 1) Note that this is a complete factorization over integers, however over Gaussian integers we can factor the last term. By default, polynomials x**n - 1 and x**n + 1 are factored using cyclotomic decomposition to speedup computations. To disable this behaviour set cyclotomic=False. 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. 427 """ cont, g = zzx_primitive(f) n = zzx_degree(g) if n <= 0: return cont, [] if poly_LC(g) < 0: cont, g = -cont, zzx_neg(g) if n == 1 or zzx_eisenstein(g): return cont, [(g, 1)] g = zzx_sqf_part(g) H, factors = None, [] if flags.get('cyclotomic', True): H = zzx_cyclotomic_factor(g) if H is None: H = zzx_zassenhaus(g) for h in H: k = 0 while True: q, r = zzx_div(f, h) if not r: f, k = q, k+1 else: break factors.append((h, k)) def compare((f_a, e_a), (f_b, e_b)): i = len(f_a) - len(f_b) if not i: j = e_a - e_b if not j: return cmp(f_a, f_b) else: return j else: return i return cont, sorted(factors, compare) def zzx_factor_sqf(f, **flags): """Factor square-free (non-primitive) polyomials in Z[x]. """ cont, g = zzx_primitive(f) n = zzx_degree(g) if n <= 0: return cont, [] if poly_LC(g) < 0: cont, g = -cont, zzx_neg(g) if n == 1 or zzx_eisenstein(g): return cont, [(g, 1)] factors = [] if flags.get('cyclotomic', True): factors = zzx_cyclotomic_factor(g) if factors is None: factors = zzx_zassenhaus(g) def compare(f_a, f_b): i = len(f_a) - len(f_b) if not i: return cmp(f_a, f_b) else: return i return cont, sorted(factors, compare) def zzx_cyclotomic_factor(f): """Efficiently factor polynomials x**n - 1 and x**n + 1 in Z[x]. Given a univariate polynomial f in Z[x] returns a list of factors of f, provided that f is in form x**n - 1 or x**n + 1 for n >= 1. Otherwise returns None. Factorization is performed using using cyclotomic decomposition of f, which makes this method much faster that any other direct factorization approach (e.g. Zassenhaus's). For more details on the implemented algorithm refer to: [1] Eric W. Weisstein, Cyclotomic Polynomial, From MathWorld - A Wolfram Web Resource, http://mathworld.wolfram.com/CyclotomicPolynomial.html """ lc_f, tc_f = poly_LC(f), poly_TC(f) if zzx_degree(f) <= 0: return None if lc_f != 1 or tc_f not in [-1, 1]: return None if any([ bool(cf) for cf in f[1:-1] ]): return None def decompose(n): H = [[1,-1]] for p, k in factorint(n).iteritems(): Q = [ zzx_quo(zzx_compose_term(h, p), h) for h in H ] H.extend(Q) for i in xrange(1, k): Q = [ zzx_compose_term(q, p) for q in Q ] H.extend(Q) return H n = zzx_degree(f) F = decompose(n) if tc_f != 1: return F else: H = [] for h in decompose(2*n): if h not in F: H.append(h) return H def zzX_wang_non_divisors(E, cu, cv): """EEZ: Compute a set of valid divisors. """ result = [ cu*cv ] for q in E: q = abs(q) for r in reversed(result): while r != 1: r = igcd(r, q) q = q // r if q == 1: return None result.append(q) return result[1:] def zzX_wang_test_points(f, V, cv, A): """EEZ: Test evaluation points for suitability. """ if not zzX_eval(poly_LC(f), A): return None U = zzX_eval(f, A) if not zzx_sqf_p(U): return None cu, u = zzx_primitive(U) if poly_LC(u) < 0: cu, u = -cu, zzx_neg(u) E = [ zzX_eval(v, A) for v, _ in V ] D = zzX_wang_non_divisors(E, cu, cv) if D is not None: return cu, u, E, D else: return None def zzX_wang_lead_coeffs(f, V, cu, E, H, A): """EEZ: Compute correct leading coefficients. """ l = poly_level(f) - 1 C, K = [], [0]*len(E) for h in H: c = zzX_const(l, 1) d = poly_LC(h)*cu for i in reversed(xrange(len(E))): k, e, (v, _) = 0, E[i], V[i] while not (d % e): d, k = d//e, k+1 if k != 0: c, K[i] = zzX_mul(c, zzX_pow(v, k)), 1 C.append(c) if any([ not k for k in K ]): raise ExtraneousFactors CC, HH = [], [] for c, h in zip(C, H): d = zzX_eval(c, A) lc = poly_LC(h) if cu == 1: cc = lc//d else: g = igcd(lc, d) d, cc = d//g, lc//g h, cu = zzx_mul_const(h, d), cu//d c = zzX_mul_const(c, cc) CC.append(c) HH.append(h) if cu == 1: return f, HH, CC CCC, HHH = [], [] for c, h in zip(CC, HH): CCC.append(zzX_mul_const(c, cu)) HHH.append(zzX_mul_const(h, cu)) f = zzX_mul_const(f, cu**(len(H)-1)) return f, HHH, CCC def zzX_wang_more_coeffs(f, C, H): pass # XXX: to be done def zzx_diophantine(F, m, p): """Solve univariate Diophantine equations. """ if len(F) == 2: a, b = F f = gf_from_int_poly(a, p) g = gf_from_int_poly(b, p) s, t, G = gf_gcdex(g, f, p) s = gf_lshift(s, m) t = gf_lshift(t, m) q, s = gf_div(s, f, p) t = gf_add_mul(t, q, g, p) s = gf_to_int_poly(s, p) t = gf_to_int_poly(t, p) result = [s, t] else: G = [F[-1]] for f in reversed(F[1:-1]): G.insert(0, zzx_mul(f, G[0])) S, T = [], [[1]] for f, g in zip(F, G): t, s = zzX_diophantine([g, f], T[-1], [], 0, p) T.append(t) S.append(s) result, S = [], S + [T[-1]] for s, f in zip(S, F): s = gf_from_int_poly(s, p) f = gf_from_int_poly(f, p) r = gf_rem(gf_lshift(s, m), f, p) s = gf_to_int_poly(r, p) result.append(s) return result def zzX_diophantine(F, c, A, d, p): """Solve multivariate Diophantine equations. """ if not A: S = [ [] for _ in F ] n = zzx_degree(c) for i, coeff in enumerate(c): if not coeff: continue T = zzx_diophantine(F, n-i, p) for j, (s, t) in enumerate(zip(S, T)): t = zzx_mul_const(t, coeff) S[j] = zzx_trunc(zzx_add(s, t), p) else: n = len(A) + 1 e = zzX_expand(*F) a, A = A[-1], A[:-1] B, G = [], [] for f in F: B.append(zzX_quo(e, f)) G.append(zzX_eval_for(f, n, a)) C = zzX_eval_for(c, n, a) S = zzX_diophantine(G, C, A, d, p) S = [ zzX_lift(1, s) for s in S ] for s, b in zip(S, B): c = zzX_sub_mul(c, s, b) c = zzX_zz_trunc(c, p) m = zzX_value(n-1, [1, -a]) M = zzX_const(n, 1) for k in xrange(0, d): if zzX_zero_p(c): break M = zzX_mul(M, m) C = zzX_diff_eval(c, n, k+1, a) if not zzX_zero_p(C): C = zzX_quo_const(C, factorial(k+1)) T = zzX_diophantine(G, C, A, d, p) for i, t in enumerate(T): T[i] = zzX_mul(zzX_lift(1, t), M) for i, (s, t) in enumerate(zip(S, T)): S[i] = zzX_add(s, t) for t, b in zip(T, B): c = zzX_sub_mul(c, t, b) c = zzX_zz_trunc(c, p) S = [ zzX_zz_trunc(s, p) for s in S ] return S def zzX_wang_hensel_lifting(f, H, LC, A, p): """EEZ: Parallel Hensel lifting algorithm. """ U, n = [f], len(A)+1 H = deepcopy(H) for i, a in enumerate(reversed(A[1:])): u = zzX_eval_for(U[0], n-i, a) U.insert(0, zzX_zz_trunc(u, p)) d = max(zzX_degree_all(f)[1:]) for j, u, a in zip(xrange(2, n+1), U, A): G = deepcopy(H) I, J = A[:j-2], A[j-1:] for i, (h, lc) in enumerate(zip(H, LC)): lc = zzX_zz_trunc(zzX_eval(lc, J), p) H[i] = [lc] + zzX_lift(1, h[1:]) m = zzX_value(j-1, [1, -a]) M = zzX_const(j, 1) c = zzX_sub(u, zzX_expand(*H)) dj = zzX_degree_for(u, j) for k in xrange(0, dj): if zzX_zero_p(c): break M = zzX_mul(M, m) C = zzX_diff_eval(c, j, k+1, a) if not zzX_zero_p(C): C = zzX_quo_const(C, factorial(k+1)) T = zzX_diophantine(G, C, I, d, p) for i, (h, t) in enumerate(zip(H, T)): h = zzX_add_mul(h, zzX_lift(1, t), M) H[i] = zzX_zz_trunc(h, p) h = zzX_sub(u, zzX_expand(*H)) c = zzX_zz_trunc(h, p) if zzX_expand(*H) != f: raise ExtraneousFactors else: return H EEZ_NUM_OK = 3 EEZ_NUM_TRY = 20 EEZ_MOD_STEP = 50 def zzX_wang(f): """Factor primitive square-free polynomials in Z[X]. Given a multivariate polynomial f in Z[x_1,...,x_n], which is primitive and square-free in x_1, computes its factorization of f into irreducibles over integers. The procedure is based on Wang's Enhanced Extended Zassenhaus algorithm. The algorithm works by viewing f as a univariate polynomial in Z[x_2,...,x_n][x_1], for which an evaluation mapping is computed: x_2 -> a_2, ..., x_n -> a_n where a_i, for i=2,...,n, are carefully chosen integers. The mapping is used to transform f into a univariate polynomial in Z[x_1], which can be factored efficiently using Zassenhaus algorithm. The last step is to lift univariate factors to obtain true multivariate factors. For this purpose a parallel Hensel lifting procedure is used. For more details on the implemented algorithm refer to: [1] P. S. Wang, An Improved Multivariate Polynomial Factoring Algorithm, Math. of Computation 32, 1978, pp. 1215--1231 [2] K. Geddes, S. R. Czapor, G. Labahn, Algorithms for Computer Algebra, Springer, 1992, pp. 264--272 """ cv, V = zzX_factor(poly_LC(f)) b = zzX_mignotte_bound(f) p = nextprime(b) l = poly_level(f) bad_points = set([]) r, mod = None, 5 while True: configs = [] while len(configs) < EEZ_NUM_OK: for i in xrange(EEZ_NUM_TRY): A = [] for j in xrange(0, l-1): A.append(randint(-mod, mod)) if tuple(A) not in bad_points: bad_points.add(tuple(A)) else: continue R = zzX_wang_test_points(f, V, cv, A) if R is not None: cu, u, E, _ = R _, H = zzx_factor_sqf(u) rr = len(H) if r is not None: if rr <= r: if rr < r: configs, r = [], rr else: continue else: r = rr configs.append((u, cu, E, H, A)) if len(configs) == EEZ_NUM_OK: break else: mod += EEZ_MOD_STEP if r == 1: return 1, [f] # irreducible u_norm, u_arg, i = None, 0, 0 for u, _, _, _, _ in configs: _u_norm = zzx_max_norm(u) if u_norm is not None: if _u_norm < u_norm: u_norm = _u_norm u_arg = i else: u_norm = _u_norm i += 1 _, cu, E, H, A = configs[u_arg] try: f, H, LC = zzX_wang_lead_coeffs(f, V, cu, E, H, A) factors = zzX_wang_hensel_lifting(f, H, LC, A, p) except ExtraneousFactors: continue negative, H = 0, [] for h in factors: _, h = zzX_zz_primitive(h) if zzX_zz_LC(h) < 0: h = zzX_neg(h) negative += 1 H.append(h) if not (negative % 2): return 1, H else: return -1, H def zzX_factor(f): """Factor (non square-free) polynomials in Z[X]. Given a multivariate polynomial f in Z[x] computes its complete factorization f_1, ..., f_n into irreducibles over integers: f = content(f) f_1**k_1 ... f_n**k_n The factorization is computed by reducing the input polynomial into a primitive square-free polynomial and factoring it using Enhanced Extended Zassenhaus (EEZ) algorithm. Trial division is used to recover the multiplicities of factors. The result is returned as a tuple consisting of: (content(f), [(f_1, k_1), ..., (f_n, k_n)) Consider polynomial f = 2*(x**2 - y**2): >>> f = [[2], [], [-2, 0, 0]] >>> zzX_factor(f) (2, [([[1], [-1, 0]], 1), ([[1], [1, 0]], 1)]) In result we got the following factorization: f = 2 (x - y) (x + y) 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. 427 """ if poly_univariate_p(f): return zzx_factor(f) cont, g = zzX_zz_primitive(f) if zzX_degree(g) < 0: return cont, [] if zzX_zz_LC(g) < 0: cont, g = -cont, zzX_neg(g) G, g = zzX_primitive(g) factors = [] if zzX_degree(g) > 0: g = zzX_sqf_part(g) s, H = zzX_wang(g) cont *= s for h in H: k = 0 while True: q, r = zzX_div(f, h) if zzX_zero_p(r): f, k = q, k+1 else: break factors.append((h, k)) for g, k in zzX_factor(G)[1]: factors.insert(0, ([g], k)) def compare((f_a, e_a), (f_b, e_b)): i = len(f_a) - len(f_b) if not i: j = e_a - e_b if not j: return cmp(f_a, f_b) else: return j else: return i return cont, sorted(factors, compare)