def poly_factors(f, *symbols, **flags): """Factor polynomials over rationals. >>> from sympy import * >>> x, y = symbols("x y") >>> poly_factors(x**2 - y**2, x, y) (1, [(Poly(x - y, x, y), 1), (Poly(x + y, x, y), 1)]) """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") denom, f = f.as_integer() if f.is_univariate: coeffs = map(int, f.iter_all_coeffs()) content, factors = zzx_factor(coeffs) for i in xrange(len(factors)): factor, k = factors[i] n = zzx_degree(factor) terms = {} for j, coeff in enumerate(factor): if coeff != 0: terms[(n - j, )] = Integer(coeff) factors[i] = Poly(terms, *f.symbols), k else: content, factors = kronecker_mv(f, **flags) return Rational(content, denom), factors
def poly_factors(f, *symbols, **flags): """Factor polynomials over rationals. >>> from sympy import * >>> x, y = symbols("x y") >>> poly_factors(x**2 - y**2, x, y) (1, [(Poly(x - y, x, y), 1), (Poly(x + y, x, y), 1)]) """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") denom, f = f.as_integer() if f.is_univariate: coeffs = map(int, f.iter_all_coeffs()) content, factors = zzx_factor(coeffs) for i in xrange(len(factors)): factor, k = factors[i] n = zzx_degree(factor) terms = {} for j, coeff in enumerate(factor): if coeff != 0: terms[(n-j,)] = Integer(coeff) factors[i] = Poly(terms, *f.symbols), k else: content, factors = kronecker_mv(f, **flags) return Rational(content, denom), factors
def test_zzx_degree(): assert zzx_degree([]) == -1 assert zzx_degree([1]) == 0 assert zzx_degree([1,0]) == 1 assert zzx_degree([1,0,0,0,1]) == 4
def test_zzX_wang(): f = zzX_from_poly(W_1) p = nextprime(zzX_mignotte_bound(f)) assert p == 6291469 V_1, k_1, E_1 = [[1],[]], 1, -14 V_2, k_2, E_2 = [[1, 0]], 2, 3 V_3, k_3, E_3 = [[1],[ 1, 0]], 2, -11 V_4, k_4, E_4 = [[1],[-1, 0]], 1, -17 V = [V_1, V_2, V_3, V_4] K = [k_1, k_2, k_3, k_4] E = [E_1, E_2, E_3, E_4] V = zip(V, K) A = [-14, 3] U = zzX_eval_list(f, A) cu, u = zzx_primitive(U) assert cu == 1 and u == U == \ [1036728, 915552, 55748, 105621, -17304, -26841, -644] assert zzX_wang_non_divisors(E, cu, 4) == [7, 3, 11, 17] assert zzx_sqf_p(u) and zzx_degree(u) == zzX_degree(f) _, H = zzx_factor_sqf(u) h_1 = [44, 42, 1] h_2 = [126, -9, 28] h_3 = [187, 0, -23] assert H == [h_1, h_2, h_3] LC_1 = [[-4], [-4,0]] LC_2 = [[-1,0,0], []] LC_3 = [[1], [], [-1,0,0]] LC = [LC_1, LC_2, LC_3] assert zzX_wang_lead_coeffs(f, V, cu, E, H, A) == (f, H, LC) H_1 = [[44L, 42L, 1L], [126L, -9L, 28L], [187L, 0L, -23L]] C_1 = [-70686, -5863, -17826, 2009, 5031, 74] H_2 = [[[-4, -12], [-3, 0], [1]], [[-9, 0], [-9], [-2, 0]], [[1, 0, -9], [], [1, -9]]] C_2 = [[9, 12, -45, -108, -324], [18, -216, -810, 0], [2, 9, -252, -288, -945], [-30, -414, 0], [2, -54, -3, 81], [12, 0]] H_3 = [[[-4, -12], [-3, 0], [1]], [[-9, 0], [-9], [-2, 0]], [[1, 0, -9], [], [1, -9]]] C_3 = [[-36, -108, 0], [-27, -36, -108], [-8, -42, 0], [-6, 0, 9], [2, 0]] T_1 = [[-3, 0], [-2], [1]] T_2 = [[[-1, 0], []], [[-3], []], [[-6]]] T_3 = [[[]], [[]], [[-1]]] assert zzX_diophantine(H_1, C_1, [], 5, p) == T_1 assert zzX_diophantine(H_2, C_2, [-14], 5, p) == T_2 assert zzX_diophantine(H_3, C_3, [-14], 5, p) == T_3 factors = zzX_wang_hensel_lifting(f, H, LC, A, p) f_1 = zzX_to_poly(factors[0], x, y, z) f_2 = zzX_to_poly(factors[1], x, y, z) f_3 = zzX_to_poly(factors[2], x, y, z) assert f_1 == -(4*(y + z)*x**2 + x*y*z - 1).as_poly(x, y, z) assert f_2 == -(y*z**2*x**2 + 3*x*z + 2*y).as_poly(x, y, z) assert f_3 == ((y**2 - z**2)*x**2 + y - z**2).as_poly(x, y, z) assert f_1*f_2*f_3 == W_1
def kronecker_mv(f, **flags): """Kronecker method for Z[X] polynomials. NOTE: This function is very slow even on small input. Use debug=True flag to see its progress, if any. """ symbols = f.symbols def mv_int_div(f, g): q = Poly((), *symbols) r = Poly((), *symbols) while not f.is_zero: lc_f, lc_g = f.LC, g.LC dv = lc_f % lc_g cf = lc_f / lc_g monom = monomial_div(f.LM, g.LM) if dv == 0 and monom is not None: q = q.add_term(cf, monom) f -= g.mul_term(cf, monom) else: r = r.add_term(*f.LT) f = f.kill_lead_term() return q, r def combinations(lisp, m): def recursion(fa, lisp, m): if m == 0: yield fa else: for i, fa2 in enumerate(lisp[0:len(lisp) + 1 - m]): for el in recursion(zzx_mul(fa2, fa), list(lisp[i + 1:]), m - 1): yield el for i, fa in enumerate(lisp[0:len(lisp) + 1 - m]): for el in recursion(fa, list(lisp[i + 1:]), m - 1): yield el debug = flags.get('debug', False) cont, f = f.as_primitive() N = len(symbols) max_exp = {} for v in symbols: max_exp[v] = 0 for coeff, monom in f.iter_terms(): for v, exp in zip(symbols, monom): if exp > max_exp[v]: max_exp[v] = exp symbols = sorted(symbols, reverse=True, key=lambda v: max_exp[v]) f = Poly(f, *symbols) d = max_exp[symbols[0]] + 1 terms, exps = {}, [] for i in xrange(0, len(symbols)): exps.append(d**i) for coeff, monom in f.iter_terms(): exp = 0 for i, expi in enumerate(monom): exp += expi * exps[i] terms[exp] = int(coeff) g, factors = zzx_from_dict(terms), [] try: for ff, k in zzx_factor(g)[1]: for i in xrange(0, k): factors.append(ff) except OverflowError: raise PolynomialError( "input too large for multivariate Kronecker method") const, result, tested = 1, [], [] if debug: print "KRONECKER-MV: Z[x] #factors = %i ..." % (len(factors)) for k in range(1, len(factors) // 2 + 1): for h in combinations(factors, k): if h in tested: continue n = zzx_degree(h) terms = {} for coeff in h: if not coeff: n = n - 1 continue else: coeff = Integer(coeff) y_deg, n = n, n - 1 monom = [0] * N for i in xrange(N): v_deg = y_deg % d y_deg = (y_deg - v_deg) // d monom[i] = v_deg monom = tuple(monom) if terms.has_key(monom): terms[monom] += coeff else: terms[monom] = coeff cand = Poly(terms, *symbols) if cand.is_one: continue if cand.LC.is_negative: cand = -cand q, r = mv_int_div(f, cand) if r.is_zero: if debug: print "KRONECKER-MV: Z[X] factor found %s" % cand result.append(cand) f = q else: tested.append(h) if f.is_constant: const, f = f.LC, Poly(1, *symbols) break if f.is_one: break if not f.is_one: if debug: print "KRONECKER-MV: Z[X] factor found %s" % f result.append(f) factors = {} for ff in result: if factors.has_key(ff): factors[ff] += 1 else: factors[ff] = 1 return cont * const, sorted(factors.items())
def test_zzx_degree(): assert zzx_degree([]) == -1 assert zzx_degree([1]) == 0 assert zzx_degree([1, 0]) == 1 assert zzx_degree([1, 0, 0, 0, 1]) == 4
def test_zzX_wang(): f = zzX_from_poly(W_1) p = nextprime(zzX_mignotte_bound(f)) assert p == 6291469 V_1, k_1, E_1 = [[1], []], 1, -14 V_2, k_2, E_2 = [[1, 0]], 2, 3 V_3, k_3, E_3 = [[1], [1, 0]], 2, -11 V_4, k_4, E_4 = [[1], [-1, 0]], 1, -17 V = [V_1, V_2, V_3, V_4] K = [k_1, k_2, k_3, k_4] E = [E_1, E_2, E_3, E_4] V = zip(V, K) A = [-14, 3] U = zzX_eval_list(f, A) cu, u = zzx_primitive(U) assert cu == 1 and u == U == \ [1036728, 915552, 55748, 105621, -17304, -26841, -644] assert zzX_wang_non_divisors(E, cu, 4) == [7, 3, 11, 17] assert zzx_sqf_p(u) and zzx_degree(u) == zzX_degree(f) _, H = zzx_factor_sqf(u) h_1 = [44, 42, 1] h_2 = [126, -9, 28] h_3 = [187, 0, -23] assert H == [h_1, h_2, h_3] LC_1 = [[-4], [-4, 0]] LC_2 = [[-1, 0, 0], []] LC_3 = [[1], [], [-1, 0, 0]] LC = [LC_1, LC_2, LC_3] assert zzX_wang_lead_coeffs(f, V, cu, E, H, A) == (f, H, LC) H_1 = [[44L, 42L, 1L], [126L, -9L, 28L], [187L, 0L, -23L]] C_1 = [-70686, -5863, -17826, 2009, 5031, 74] H_2 = [[[-4, -12], [-3, 0], [1]], [[-9, 0], [-9], [-2, 0]], [[1, 0, -9], [], [1, -9]]] C_2 = [[9, 12, -45, -108, -324], [18, -216, -810, 0], [2, 9, -252, -288, -945], [-30, -414, 0], [2, -54, -3, 81], [12, 0]] H_3 = [[[-4, -12], [-3, 0], [1]], [[-9, 0], [-9], [-2, 0]], [[1, 0, -9], [], [1, -9]]] C_3 = [[-36, -108, 0], [-27, -36, -108], [-8, -42, 0], [-6, 0, 9], [2, 0]] T_1 = [[-3, 0], [-2], [1]] T_2 = [[[-1, 0], []], [[-3], []], [[-6]]] T_3 = [[[]], [[]], [[-1]]] assert zzX_diophantine(H_1, C_1, [], 5, p) == T_1 assert zzX_diophantine(H_2, C_2, [-14], 5, p) == T_2 assert zzX_diophantine(H_3, C_3, [-14], 5, p) == T_3 factors = zzX_wang_hensel_lifting(f, H, LC, A, p) f_1 = zzX_to_poly(factors[0], x, y, z) f_2 = zzX_to_poly(factors[1], x, y, z) f_3 = zzX_to_poly(factors[2], x, y, z) assert f_1 == -(4 * (y + z) * x**2 + x * y * z - 1).as_poly(x, y, z) assert f_2 == -(y * z**2 * x**2 + 3 * x * z + 2 * y).as_poly(x, y, z) assert f_3 == ((y**2 - z**2) * x**2 + y - z**2).as_poly(x, y, z) assert f_1 * f_2 * f_3 == W_1
def kronecker_mv(f, **flags): """Kronecker method for Z[X] polynomials. NOTE: This function is very slow even on small input. Use debug=True flag to see its progress, if any. """ symbols = f.symbols def mv_int_div(f, g): q = Poly((), *symbols) r = Poly((), *symbols) while not f.is_zero: lc_f, lc_g = f.LC, g.LC dv = lc_f % lc_g cf = lc_f / lc_g monom = monomial_div(f.LM, g.LM) if dv == 0 and monom is not None: q = q.add_term(cf, monom) f -= g.mul_term(cf, monom) else: r = r.add_term(*f.LT) f = f.kill_lead_term() return q, r def combinations(lisp, m): def recursion(fa, lisp, m): if m == 0: yield fa else: for i, fa2 in enumerate(lisp[0 : len(lisp) + 1 - m]): for el in recursion(zzx_mul(fa2, fa), list(lisp[i + 1:]), m - 1): yield el for i, fa in enumerate(lisp[0 : len(lisp) + 1 - m]): for el in recursion(fa, list(lisp[i + 1:]), m - 1): yield el debug = flags.get('debug', False) cont, f = f.as_primitive() N = len(symbols) max_exp = {} for v in symbols: max_exp[v] = 0 for coeff, monom in f.iter_terms(): for v, exp in zip(symbols, monom): if exp > max_exp[v]: max_exp[v] = exp symbols = sorted(symbols, reverse=True, key=lambda v: max_exp[v]) f = Poly(f, *symbols) d = max_exp[symbols[0]] + 1 terms, exps = {}, [] for i in xrange(0, len(symbols)): exps.append(d**i) for coeff, monom in f.iter_terms(): exp = 0 for i, expi in enumerate(monom): exp += expi * exps[i] terms[exp] = int(coeff) g, factors = zzx_from_dict(terms), [] try: for ff, k in zzx_factor(g)[1]: for i in xrange(0, k): factors.append(ff) except OverflowError: raise PolynomialError("input too large for multivariate Kronecker method") const, result, tested = 1, [], [] if debug: print "KRONECKER-MV: Z[x] #factors = %i ..." % (len(factors)) for k in range(1, len(factors)//2 + 1): for h in combinations(factors, k): if h in tested: continue n = zzx_degree(h) terms = {} for coeff in h: if not coeff: n = n-1 continue else: coeff = Integer(coeff) y_deg, n = n, n-1 monom = [0] * N for i in xrange(N): v_deg = y_deg % d y_deg = (y_deg - v_deg) // d monom[i] = v_deg monom = tuple(monom) if terms.has_key(monom): terms[monom] += coeff else: terms[monom] = coeff cand = Poly(terms, *symbols) if cand.is_one: continue if cand.LC.is_negative: cand = -cand; q, r = mv_int_div(f, cand) if r.is_zero: if debug: print "KRONECKER-MV: Z[X] factor found %s" % cand result.append(cand) f = q else: tested.append(h) if f.is_constant: const, f = f.LC, Poly(1, *symbols) break if f.is_one: break if not f.is_one: if debug: print "KRONECKER-MV: Z[X] factor found %s" % f result.append(f) factors = {} for ff in result: if factors.has_key(ff): factors[ff] += 1 else: factors[ff] = 1 return cont*const, sorted(factors.items())