Copyright (c) 2011 Microsoft Corporation
Module Name:
polynomial_factorization.cpp
Abstract:
Implementation of polynomial factorization.
Author:
Dejan (t-dejanj) 2011-11-15
Notes:
[1] Elwyn Ralph Berlekamp. Factoring Polynomials over Finite Fields. Bell System Technical Journal,
46(8-10):1853-1859, 1967.
[2] Donald Ervin Knuth. The Art of Computer Programming, volume 2: Seminumerical Algorithms. Addison Wesley, third
edition, 1997.
[3] Henri Cohen. A Course in Computational Algebraic Number Theory. Springer Verlag, 1993.
--*/
#include "util/trace.h"
#include "util/util.h"
#include "math/polynomial/upolynomial_factorization_int.h"
#include "util/prime_generator.h"
using namespace std;
namespace upolynomial {
unsigned get_p_from_manager(zp_numeral_manager const & zp_nm) {
z_numeral_manager & nm = zp_nm.m();
numeral const & p = zp_nm.p();
if (!nm.is_uint64(p)) {
throw upolynomial_exception("The prime number attempted in factorization is too big!");
}
uint64_t p_uint64 = nm.get_uint64(p);
unsigned p_uint = static_cast<unsigned>(p_uint64);
if (((uint64_t)p_uint) != p_uint64) {
throw upolynomial_exception("The prime number attempted in factorization is too big!");
}
return p_uint;
}
\brief The Q-I matrix from Berelkamp's algorithm [1,2].
Given a polynomial f = \sum f_k x^k of degree n, with f_i in Z_p[x], the i-th row of Q is a representation of
x^(p*i) modulo f, i.e.
x^(p*i) modulo f = \sum_j q[i][j] x^j
If f is of degree n, the matrix is square nxn. When the this matrix is constructed we obtain Q - I, because
this is what we need in the algorithm. After construction, the null space vectors can be extracted one-by one using
the next_null_space_vector method.
*/
class berlekamp_matrix {
zp_manager & m_upm;
mpzzp_manager & m_zpm;
svector<mpz> m_matrix;
unsigned m_size;
unsigned m_null_row;
svector<int> m_column_pivot;
svector<int> m_row_pivot;
mpz & get(unsigned i, unsigned j) {
SASSERT(i < m_size && j < m_size);
return m_matrix[i*m_size + j];
}
mpz const & get(unsigned i, unsigned j) const {
SASSERT(i < m_size && j < m_size);
return m_matrix[i*m_size + j];
}
public:
\brief Construct the matrix as explained above, f should be in the Z_p.
*/
berlekamp_matrix(zp_manager & upm, numeral_vector const & f)
: m_upm(upm),
m_zpm(upm.m()),
m_size(m_upm.degree(f)),
m_null_row(1),
m_column_pivot(m_size, -1),
m_row_pivot(m_size, -1) {
unsigned p = get_p_from_manager(m_zpm);
TRACE("polynomial::factorization::bughunt", tout << "polynomial::berlekamp_matrix("; m_upm.display(tout, f); tout << ", " << p << ")" << endl;);
m_matrix.push_back(1);
for (unsigned j = 0; j < m_size; ++ j) {
m_matrix.push_back(0);
}
scoped_numeral tmp(m_zpm);
unsigned row = 0, previous_row = 0;
for (unsigned k = 1; true; previous_row = row, ++ k) {
if (k % p == 1) {
if (++ row >= m_size) {
break;
}
for (unsigned j = 0; j < m_size; ++ j) {
m_matrix.push_back(0);
}
}
m_zpm.set(tmp, get(previous_row, m_size - 1));
for (unsigned j = m_size - 1; j > 0; -- j) {
m_zpm.submul(get(previous_row, j-1), tmp, f[j], get(row, j));
}
m_zpm.mul(f[0], tmp, get(row, 0));
m_zpm.neg(get(row, 0));
}
for (unsigned i = 0; i < m_size; ++ i) {
m_zpm.dec(get(i, i));
}
TRACE("polynomial::factorization::bughunt", tout << "polynomial::berlekamp_matrix():" << endl; display(tout); tout << endl;);
}
\brief Destructor, just removing the numbers
*/
~berlekamp_matrix() {
for (unsigned k = 0; k < m_matrix.size(); ++ k) {
m_zpm.del(m_matrix[k]);
}
}
\brief 'Diagonalizes' the matrix using only column operations. The resulting matrix will have -1 at pivot
elements. Returns the rank of the null space.
*/
unsigned diagonalize() {
scoped_numeral multiplier(m_zpm);
unsigned null_rank = 0;
for (unsigned i = 0; i < m_size; ++ i) {
bool column_found = false;
for (unsigned j = 0; j < m_size; ++ j) {
if (m_column_pivot[j] < 0 && !m_zpm.is_zero(get(i, j))) {
column_found = true;
m_column_pivot[j] = i;
m_row_pivot[i] = j;
m_zpm.set(multiplier, get(i, j));
m_zpm.inv(multiplier);
m_zpm.neg(multiplier);
for (unsigned k = m_null_row; k < m_size; ++ k) {
m_zpm.mul(get(k, j), multiplier, get(k, j));
}
for (unsigned other_j = 0; other_j < m_size; ++ other_j) {
if (j != other_j) {
m_zpm.set(multiplier, get(i, other_j));
for (unsigned k = m_null_row; k < m_size; ++ k) {
m_zpm.addmul(get(k, other_j), multiplier, get(k, j), get(k, other_j));
}
}
}
}
}
if (!column_found) {
null_rank ++;
}
}
TRACE("polynomial::factorization::bughunt", tout << "polynomial::diagonalize():" << endl; display(tout); tout << endl;);
return null_rank;
}
If rank of the matrix is n - r, we are interested in linearly independent vectors v_1, ..., v_r (the basis of
the null space), such that v_k A = 0. This method will give one at a time. The method returns true if vector has
been computed properly. The first vector [1, 0, ..., 0] is ignored (m_null_row starts from 1).
*/
bool next_null_space_vector(numeral_vector & v) {
SASSERT(v.size() <= m_size);
v.resize(m_size);
for (; m_null_row < m_size; ++ m_null_row) {
if (m_row_pivot[m_null_row] < 0) {
for (unsigned j = 0; j < m_size; ++ j) {
if (m_row_pivot[j] >= 0) {
m_zpm.set(v[j], get(m_null_row, m_row_pivot[j]));
}
else {
if (j == m_null_row) {
m_zpm.set(v[j], 1);
}
else {
m_zpm.set(v[j], 0);
}
}
}
++ m_null_row;
return true;
}
}
return false;
}
\brief Display the matrix on the output stream.
*/
void display(std::ostream & out) const {
for (unsigned i = 0; i < m_matrix.size() / m_size; ++ i) {
for (unsigned j = 0; j < m_size; ++ j) {
out << m_zpm.to_string(get(i, j)) << "\t";
}
out << endl;
}
}
};
See [3] p. 125.
*/
void zp_square_free_factor(zp_manager & upm, numeral_vector const & f, zp_factors & sq_free_factors) {
zp_numeral_manager & nm = upm.m();
unsigned p = get_p_from_manager(upm.m());
TRACE("polynomial::factorization", tout << "polynomial::square_free_factor("; upm.display(tout, f); tout << ") over Z_" << p << endl;);
scoped_numeral_vector div_tmp(nm);
SASSERT(f.size() > 1);
scoped_numeral_vector T_0(nm);
upm.set(f.size(), f.data(), T_0);
scoped_numeral constant(nm);
upm.mk_monic(T_0.size(), T_0.data(), constant);
sq_free_factors.set_constant(constant);
TRACE("polynomial::factorization::bughunt",
tout << "Initial factors: " << sq_free_factors << endl;
tout << "R.<x> = GF(" << p << ")['x']" << endl;
tout << "T_0 = "; upm.display(tout, T_0); tout << endl;
);
unsigned e = 1;
scoped_numeral_vector T_0_d(nm);
scoped_numeral_vector T(nm);
scoped_numeral_vector V(nm);
scoped_numeral_vector W(nm);
scoped_numeral_vector A_ek(nm);
while (T_0.size() > 1)
{
unsigned k = 0;
TRACE("polynomial::factorization::bughunt", tout << "k = 0" << endl;);
upm.derivative(T_0.size(), T_0.data(), T_0_d);
TRACE("polynomial::factorization::bughunt",
tout << "T_0_d = T_0.derivative(x)" << endl;
tout << "T_0_d == "; upm.display(tout, T_0_d); tout << endl;
);
upm.gcd(T_0.size(), T_0.data(), T_0_d.size(), T_0_d.data(), T);
TRACE("polynomial::factorization::bughunt",
tout << "T = T_0.gcd(T_0_d)" << endl;
tout << "T == "; upm.display(tout, T); tout << endl;
);
upm.div(T_0.size(), T_0.data(), T.size(), T.data(), V);
TRACE("polynomial::factorization::bughunt",
tout << "V = T_0.quo_rem(T)[0]" << endl;
tout << "V == "; upm.display(tout, V); tout << endl;
);
while (V.size() > 1) {
if ((++k) % p == 0) {
++ k;
upm.div(T.size(), T.data(), V.size(), V.data(), T);
TRACE("polynomial::factorization::bughunt",
tout << "T = T.quo_rem(V)[0]" << endl;
tout << "T == "; upm.display(tout, T); tout << endl;
);
}
upm.gcd(T.size(), T.data(), V.size(), V.data(), W);
TRACE("polynomial::factorization::bughunt",
tout << "W = T.gcd(V)" << endl;
upm.display(tout, W); tout << endl;
);
upm.div(V.size(), V.data(), W.size(), W.data(), A_ek);
TRACE("polynomial::factorization::bughunt",
tout << "A_ek = V.quo_rem(W)[0]" << endl;
tout << "A_ek == "; upm.display(tout, A_ek); tout << endl;
);
V.swap(W);
TRACE("polynomial::factorization::bughunt",
tout << "V = W" << endl;
tout << "V == "; upm.display(tout, V); tout << endl;
);
upm.div(T.size(), T.data(), V.size(), V.data(), T);
TRACE("polynomial::factorization::bughunt",
tout << "T = T.quo_rem(V)[0]" << endl;
tout << "T == "; upm.display(tout, T); tout << endl;
);
if (A_ek.size() > 1) {
TRACE("polynomial::factorization::bughunt", tout << "Factor: ("; upm.display(tout, A_ek); tout << ")^" << e*k << endl;);
sq_free_factors.push_back(A_ek, e*k);
}
}
e *= p;
T_0.reset();
for (unsigned deg_p = 0; deg_p < T.size(); deg_p += p) {
T_0.push_back(numeral());
nm.set(T_0.back(), T[deg_p]);
}
TRACE("polynomial::factorization::bughunt",
tout << "T_0 = "; upm.display(tout, T_0); tout << endl;
);
}
TRACE("polynomial::factorization", tout << "polynomial::square_free_factor("; upm.display(tout, f); tout << ") => " << sq_free_factors << endl;);
}
bool zp_factor(zp_manager & upm, numeral_vector const & f, zp_factors & factors) {
TRACE("polynomial::factorization",
unsigned p = get_p_from_manager(upm.m());
tout << "polynomial::factor("; upm.display(tout, f); tout << ") over Z_" << p << endl;);
zp_factors sq_free_factors(upm);
zp_square_free_factor(upm, f, sq_free_factors);
for (unsigned i = 0; i < sq_free_factors.distinct_factors(); ++ i) {
unsigned j = factors.distinct_factors();
if (upm.degree(sq_free_factors[i]) > 1) {
zp_factor_square_free(upm, sq_free_factors[i], factors);
for (; j < factors.distinct_factors(); ++ j) {
factors.set_degree(j, sq_free_factors.get_degree(i)*factors.get_degree(j));
}
}
else {
factors.push_back(sq_free_factors[i], sq_free_factors.get_degree(i));
}
}
factors.set_constant(sq_free_factors.get_constant());
TRACE("polynomial::factorization", tout << "polynomial::factor("; upm.display(tout, f); tout << ") => " << factors << endl;);
return factors.total_factors() > 1;
}
bool zp_factor_square_free(zp_manager & upm, numeral_vector const & f, zp_factors & factors) {
return zp_factor_square_free_berlekamp(upm, f, factors, false);
}
bool zp_factor_square_free_berlekamp(zp_manager & upm, numeral_vector const & f, zp_factors & factors, bool randomized) {
SASSERT(upm.degree(f) > 1);
mpzzp_manager & zpm = upm.m();
unsigned p = get_p_from_manager(zpm);
TRACE("polynomial::factorization", tout << "upolynomial::factor_square_free_berlekamp("; upm.display(tout, f); tout << ", " << p << ")" << endl;);
SASSERT(zpm.is_one(f.back()));
berlekamp_matrix Q_I(upm, f);
unsigned first_factor = factors.distinct_factors();
factors.push_back(f, 1);
unsigned r = Q_I.diagonalize();
if (r == 1) {
TRACE("polynomial::factorization", tout << "upolynomial::factor_square_free_berlekamp("; upm.display(tout, f); tout << ", " << p << ") => " << factors << endl;);
return false;
}
TRACE("polynomial::factorization::bughunt", tout << "upolynomial::factor_square_free_berlekamp(): computing factors, expecting " << r << endl;);
scoped_numeral_vector gcd(zpm);
scoped_numeral_vector div(zpm);
unsigned d = upm.degree(f);
(void)d;
scoped_numeral_vector v_k(zpm);
while (Q_I.next_null_space_vector(v_k)) {
TRACE("polynomial::factorization::bughunt",
tout << "null vector: ";
for(unsigned j = 0; j < d; ++ j) {
tout << zpm.to_string(v_k[j]) << " ";
}
tout << endl;
);
upm.trim(v_k);
unsigned current_factor_end = factors.distinct_factors();
for (unsigned current_factor_i = first_factor; current_factor_i < current_factor_end; ++ current_factor_i) {
if (factors[current_factor_i].size() == 2) {
continue;
}
for (unsigned s = 0; s < p; ++ s) {
numeral_vector const & current_factor = factors[current_factor_i];
zpm.dec(v_k[0]);
upm.gcd(v_k.size(), v_k.data(), current_factor.size(), current_factor.data(), gcd);
if (gcd.size() != 1 && gcd.size() != current_factor.size()) {
upm.div(current_factor.size(), current_factor.data(), gcd.size(), gcd.data(), div);
TRACE("polynomial::factorization::bughunt",
tout << "current_factor = "; upm.display(tout, current_factor); tout << endl;
tout << "gcd_norm = "; upm.display(tout, gcd); tout << endl;
tout << "div = "; upm.display(tout, div); tout << endl;
);
factors.swap_factor(current_factor_i, div);
factors.push_back(gcd, 1);
}
if (factors.distinct_factors() - first_factor == r) {
TRACE("polynomial::factorization", tout << "polynomial::factor("; upm.display(tout, f); tout << ", " << p << ") => " << factors << " of degree " << factors.get_degree() << endl;);
return true;
}
}
}
}
SASSERT(false);
return true;
}
Check if the hensel lifting was correct, i.e. that C = A*B (mod br).
*/
bool check_hansel_lift(z_manager & upm, numeral_vector const & C,
numeral const & a, numeral const & b, numeral const & r,
numeral_vector const & A, numeral_vector const & B,
numeral_vector const & A_lifted, numeral_vector const & B_lifted)
{
z_numeral_manager & nm = upm.zm();
scoped_mpz br(nm);
nm.mul(b, r, br);
zp_manager br_upm(upm.lim(), upm.zm());
br_upm.set_zp(br);
if (A_lifted.size() != A.size()) return false;
if (B_lifted.size() != B.size()) return false;
if (!nm.eq(A.back(), A_lifted.back())) return false;
scoped_mpz_vector test1(nm);
upm.mul(A_lifted.size(), A_lifted.data(), B_lifted.size(), B_lifted.data(), test1);
upm.sub(C.size(), C.data(), test1.size(), test1.data(), test1);
to_zp_manager(br_upm, test1);
if (!test1.empty()) {
TRACE("polynomial::factorization::bughunt",
tout << "sage: R.<x> = ZZ['x']" << endl;
tout << "sage: A = "; upm.display(tout, A); tout << endl;
tout << "sage: B = "; upm.display(tout, B); tout << endl;
tout << "sage: C = "; upm.display(tout, C); tout << endl;
tout << "sage: test1 = C - AB" << endl;
tout << "sage: print test1.change_ring(GF(" << nm.to_string(br) << "))" << endl;
tout << "sage: print 'expected 0'" << endl;
);
return false;
}
zp_manager b_upm(upm.lim(), nm);
b_upm.set_zp(b);
scoped_mpz_vector test2a(nm), test2b(nm);
to_zp_manager(b_upm, A, test2a);
to_zp_manager(b_upm, A_lifted, test2b);
if (!upm.eq(test2a, test2b)) {
return false;
}
scoped_mpz_vector test3a(nm), test3b(nm);
to_zp_manager(b_upm, B, test3a);
to_zp_manager(b_upm, B_lifted, test3b);
if (!upm.eq(test3a, test3b)) {
return false;
}
return true;
}
Performs a Hensel lift of A and B in Z_a to Z_b, where p is prime and a = p^{a_k}, b = p^{b_k},
r = (a, b), with the following assumptions:
(1) UA + VB = 1 (mod a)
(2) C = A*B (mod b)
(3) (l(A), r) = 1 (important in order to divide by A, i.e. to invert l(A))
(4) deg(A) + deg(B) = deg(C)
The output of is two polynomials A1, B1 such that A1 = A (mod b), B1 = B (mod b),
l(A1) = l(A), deg(A1) = deg(A), deg(B1) = deg(B) and C = A1 B1 (mod b*r). Such A1, B1 are unique if
r is prime. See [3] p. 138.
*/
void hensel_lift(z_manager & upm, numeral const & a, numeral const & b, numeral const & r,
numeral_vector const & U, numeral_vector const & A, numeral_vector const & V, numeral_vector const & B,
numeral_vector const & C, numeral_vector & A_lifted, numeral_vector & B_lifted) {
z_numeral_manager & nm = upm.zm();
TRACE("polynomial::factorization::bughunt",
tout << "polynomial::hensel_lift(";
tout << "a = " << nm.to_string(a) << ", ";
tout << "b = " << nm.to_string(b) << ", ";
tout << "r = " << nm.to_string(r) << ", ";
tout << "U = "; upm.display(tout, U); tout << ", ";
tout << "A = "; upm.display(tout, A); tout << ", ";
tout << "V = "; upm.display(tout, V); tout << ", ";
tout << "B = "; upm.display(tout, B); tout << ", ";
tout << "C = "; upm.display(tout, C); tout << ")" << endl;
);
zp_manager r_upm(upm.lim(), nm);
r_upm.set_zp(r);
SASSERT(upm.degree(C) == upm.degree(A) + upm.degree(B));
SASSERT(upm.degree(U) < upm.degree(B) && upm.degree(V) < upm.degree(A));
scoped_numeral_vector f(upm.m());
upm.mul(A.size(), A.data(), B.size(), B.data(), f);
upm.sub(C.size(), C.data(), f.size(), f.data(), f);
upm.div(f, b);
to_zp_manager(r_upm, f);
TRACE("polynomial::factorization",
tout << "f = "; upm.display(tout, f); tout << endl;
);
scoped_numeral_vector Vf(r_upm.m()), t(r_upm.m()), S(r_upm.m());
TRACE("polynomial::factorization::bughunt",
tout << "V == "; upm.display(tout, V); tout << endl;
);
r_upm.mul(V.size(), V.data(), f.size(), f.data(), Vf);
TRACE("polynomial::factorization::bughunt",
tout << "Vf = V*f" << endl;
tout << "Vf == "; upm.display(tout, Vf); tout << endl;
);
r_upm.div_rem(Vf.size(), Vf.data(), A.size(), A.data(), t, S);
TRACE("polynomial::factorization::bughunt",
tout << "[t, S] = Vf.quo_rem(A)" << endl;
tout << "t == "; upm.display(tout, t); tout << endl;
tout << "S == "; upm.display(tout, S); tout << endl;
);
scoped_numeral_vector T(r_upm.m()), tmp(r_upm.m());
r_upm.mul(U.size(), U.data(), f.size(), f.data(), T);
TRACE("polynomial::factorization::bughunt",
tout << "T == U*f" << endl;
tout << "T == "; upm.display(tout, T); tout << endl;
);
r_upm.mul(B.size(), B.data(), t.size(), t.data(), tmp);
TRACE("polynomial::factorization::bughunt",
tout << "tmp = B*t" << endl;
tout << "tmp == "; upm.display(tout, tmp); tout << endl;
);
r_upm.add(T.size(), T.data(), tmp.size(), tmp.data(), T);
TRACE("polynomial::factorization::bughunt",
tout << "T = B*tmp" << endl;
tout << "T == "; upm.display(tout, T); tout << endl;
);
upm.mul(S, b);
upm.mul(T, b);
upm.add(A.size(), A.data(), S.size(), S.data(), A_lifted);
upm.add(B.size(), B.data(), T.size(), T.data(), B_lifted);
CASSERT("polynomial::factorizatio::bughunt", check_hansel_lift(upm, C, a, b, r, A, B, A_lifted, B_lifted));
}
bool check_quadratic_hensel(zp_manager & zpe_upm, numeral_vector const & U, numeral_vector const & A, numeral_vector const & V, numeral_vector const & B) {
z_numeral_manager & nm = zpe_upm.zm();
scoped_mpz_vector tmp1(nm);
scoped_mpz_vector tmp2(nm);
zpe_upm.mul(U.size(), U.data(), A.size(), A.data(), tmp1);
zpe_upm.mul(V.size(), V.data(), B.size(), B.data(), tmp2);
scoped_mpz_vector one(nm);
zpe_upm.add(tmp1.size(), tmp1.data(), tmp2.size(), tmp2.data(), one);
if (one.size() != 1 || !nm.is_one(one[0])) {
TRACE("polynomial::factorization::bughunt",
tout << "sage: R.<x> = Zmod(" << nm.to_string(zpe_upm.m().p()) << ")['x']" << endl;
tout << "sage: A = "; zpe_upm.display(tout, A); tout << endl;
tout << "sage: B = "; zpe_upm.display(tout, B); tout << endl;
tout << "sage: U = "; zpe_upm.display(tout, U); tout << endl;
tout << "sage: V = "; zpe_upm.display(tout, V); tout << endl;
tout << "sage: print (1 - UA - VB)" << endl;
tout << "sage: print 'expected 0'" << endl;
);
return false;
}
return true;
}
Lift C = A*B from Z_p[x] to Z_{p^e}[x] such that:
* A = A_lift (mod p)
* B = B_lift (mod p)
* C = A*B (mod p^e)
*/
void hensel_lift_quadratic(z_manager& upm, numeral_vector const & C,
zp_manager & zpe_upm, numeral_vector & A, numeral_vector & B, unsigned e) {
z_numeral_manager & nm = upm.zm();
TRACE("polynomial::factorization::bughunt",
tout << "polynomial::hansel_lift_quadratic(";
tout << "A = "; upm.display(tout, A); tout << ", ";
tout << "B = "; upm.display(tout, B); tout << ", ";
tout << "C = "; upm.display(tout, C); tout << ", ";
tout << "p = " << nm.to_string(zpe_upm.m().p()) << ", e = " << e << ")" << endl;
);
zp_manager zp_upm(upm.lim(), nm);
zp_upm.set_zp(zpe_upm.m().p());
scoped_mpz_vector U(nm), V(nm), D(nm);
zp_upm.ext_gcd(A.size(), A.data(), B.size(), B.data(), U, V, D);
SASSERT(D.size() == 1 && zp_upm.m().is_one(D[0]));
scoped_mpz_vector A_lifted(nm), B_lifted(nm);
for (unsigned k = 1; k < e; k *= 2) {
upm.checkpoint();
numeral const & pe = zpe_upm.m().p();
hensel_lift(upm, pe, pe, pe, U, A, V, B, C, A_lifted, B_lifted);
TRACE("polynomial::factorization::bughunt",
tout << "A_lifted = "; upm.display(tout, A_lifted); tout << endl;
tout << "B_lifted = "; upm.display(tout, B_lifted); tout << endl;
tout << "C = "; upm.display(tout, C); tout << endl;
);
scoped_mpz_vector tmp1(nm), g(nm);
g.push_back(numeral());
nm.set(g.back(), 1);
upm.mul(A_lifted.size(), A_lifted.data(), U.size(), U.data(), tmp1);
upm.sub(g.size(), g.data(), tmp1.size(), tmp1.data(), g);
upm.mul(B_lifted.size(), B_lifted.data(), V.size(), V.data(), tmp1);
upm.sub(g.size(), g.data(), tmp1.size(), tmp1.data(), g);
upm.div(g, pe);
to_zp_manager(zpe_upm, g);
TRACE("polynomial::factorization::bughunt",
tout << "g = (1 - A_lifted*U - B_lifted*V)/" << nm.to_string(pe) << endl;
tout << "g == "; upm.display(tout, g); tout << endl;
);
scoped_mpz_vector S(nm), T(nm), t(nm), tmp2(nm);
zpe_upm.mul(g.size(), g.data(), V.size(), V.data(), tmp1);
zpe_upm.div_rem(tmp1.size(), tmp1.data(), A.size(), A.data(), t, T);
zpe_upm.mul(g.size(), g.data(), U.size(), U.data(), tmp1);
zpe_upm.mul(t.size(), t.data(), B.size(), B.data(), tmp2);
zpe_upm.add(tmp1.size(), tmp1.data(), tmp2.size(), tmp2.data(), S);
upm.mul(S.size(), S.data(), pe);
upm.mul(T.size(), T.data(), pe);
upm.add(U.size(), U.data(), S.size(), S.data(), U);
upm.add(V.size(), V.data(), T.size(), T.data(), V);
zpe_upm.m().set_p_sq();
to_zp_manager(zpe_upm, U);
to_zp_manager(zpe_upm, V);
to_zp_manager(zpe_upm, A_lifted);
to_zp_manager(zpe_upm, B_lifted);
A.swap(A_lifted);
B.swap(B_lifted);
CASSERT("polynomial::factorizatio::bughunt", check_quadratic_hensel(zpe_upm, U, A, V, B));
}
}
bool check_hensel_lift(z_manager & upm, numeral_vector const & f, zp_factors const & zp_fs, zp_factors const & zpe_fs, unsigned e) {
numeral_manager & nm(upm.m());
zp_manager & zp_upm = zp_fs.upm();
zp_manager & zpe_upm = zpe_fs.upm();
numeral const & p = zp_fs.nm().p();
numeral const & pe = zpe_fs.nm().p();
scoped_numeral power(nm);
nm.power(p, e, power);
if (!nm.ge(pe, power)) {
return false;
}
scoped_numeral_vector mult_zp(nm), f_zp(nm);
zp_fs.multiply(mult_zp);
to_zp_manager(zp_upm, f, f_zp);
zp_upm.mul(mult_zp, f_zp.back());
if (!upm.eq(mult_zp, f_zp)) {
TRACE("polynomial::factorization::bughunt",
tout << "f = "; upm.display(tout, f); tout << endl;
tout << "zp_fs = " << zp_fs << endl;
tout << "sage: R.<x> = Zmod(" << nm.to_string(p) << ")['x']" << endl;
tout << "sage: mult_zp = "; upm.display(tout, mult_zp); tout << endl;
tout << "sage: f_zp = "; upm.display(tout, f_zp); tout << endl;
tout << "sage: mult_zp == f_zp" << endl;
);
return false;
}
if (zpe_fs.distinct_factors() != zp_fs.distinct_factors()) {
return false;
}
scoped_numeral_vector mult_zpe(nm), f_zpe(nm);
zpe_fs.multiply(mult_zpe);
to_zp_manager(zpe_upm, f, f_zpe);
zpe_upm.mul(mult_zpe, f_zpe.back());
if (!upm.eq(mult_zpe, f_zpe)) {
TRACE("polynomial::factorization::bughunt",
tout << "f = "; upm.display(tout, f); tout << endl;
tout << "zpe_fs = " << zpe_fs << endl;
tout << "sage: R.<x> = Zmod(" << nm.to_string(pe) << ")['x']" << endl;
tout << "sage: mult_zpe = "; upm.display(tout, mult_zpe); tout << endl;
tout << "sage: f_zpe = "; upm.display(tout, f_zpe); tout << endl;
tout << "sage: mult_zpe == f_zpe" << endl;
);
return false;
}
return true;
}
bool check_individual_lift(zp_manager & zp_upm, numeral_vector const & A_p, zp_manager & zpe_upm, numeral_vector const & A_pe) {
scoped_numeral_vector A_pe_p(zp_upm.m());
to_zp_manager(zp_upm, A_pe, A_pe_p);
if (!zp_upm.eq(A_p, A_pe_p)) {
return false;
}
return true;
}
void hensel_lift(z_manager & upm, numeral_vector const & f, zp_factors const & zp_fs, unsigned e, zp_factors & zpe_fs) {
SASSERT(zp_fs.total_factors() > 0);
zp_numeral_manager & zp_nm = zp_fs.nm();
zp_manager & zp_upm = zp_fs.upm();
z_numeral_manager & nm = zp_nm.m();
SASSERT(nm.is_one(zp_fs.get_constant()));
zp_numeral_manager & zpe_nm = zpe_fs.nm();
zp_manager & zpe_upm = zpe_fs.upm();
zpe_nm.set_zp(zp_nm.p());
TRACE("polynomial::factorization::bughunt",
tout << "polynomial::hansel_lift("; upm.display(tout, f); tout << ", " << zp_fs << ")" << endl;
);
scoped_mpz_vector A(nm), B(nm), C(nm), f_parts(nm);
upm.set(f.size(), f.data(), f_parts);
for (int i = 0, i_end = zp_fs.distinct_factors()-1; i < i_end; ++ i) {
SASSERT(zp_fs.get_degree(i) == 1);
zp_upm.set(zp_fs[i].size(), zp_fs[i].data(), A);
TRACE("polynomial::factorization::bughunt",
tout << "A = "; upm.display(tout, A); tout << endl;
);
if (i > 0) {
to_zp_manager(zp_upm, f_parts, C);
}
else {
zp_fs.multiply(C);
scoped_mpz lc(nm);
zp_nm.set(lc, f.back());
zp_upm.mul(C, lc);
}
TRACE("polynomial::factorization::bughunt",
tout << "C = "; upm.display(tout, C); tout << endl;
);
zp_upm.div(C.size(), C.data(), A.size(), A.data(), B);
TRACE("polynomial::factorization::bughunt",
tout << "B = "; upm.display(tout, B); tout << endl;
);
zpe_nm.set_zp(zp_nm.p());
hensel_lift_quadratic(upm, f_parts, zpe_upm, A, B, e);
CASSERT("polynomial::factorizatio::bughunt", check_individual_lift(zp_upm, zp_fs[i], zpe_upm, A));
TRACE("polynomial::factorization",
tout << "lifted to " << nm.to_string(zpe_upm.m().p()) << endl;
tout << "A = "; upm.display(tout, A); tout << endl;
tout << "B = "; upm.display(tout, B); tout << endl;
);
if (i == 0) {
to_zp_manager(zpe_upm, f, f_parts);
}
zpe_upm.div(f_parts.size(), f_parts.data(), A.size(), A.data(), f_parts);
zpe_fs.push_back_swap(A, 1);
}
scoped_mpz lc_inv(nm);
zpe_nm.set(lc_inv, f.back());
zpe_nm.inv(lc_inv);
zpe_upm.mul(B, lc_inv);
zpe_fs.push_back_swap(B, 1);
TRACE("polynomial::factorization::bughunt",
tout << "polynomial::hansel_lift("; upm.display(tout, f); tout << ", " << zp_fs << ") => " << zpe_fs << endl;
);
CASSERT("polynomial::factorizatio::bughunt", check_hensel_lift(upm, f, zp_fs, zpe_fs, e));
}
static unsigned mignotte_bound(z_manager & upm, numeral_vector const & f, numeral const & p) {
numeral_manager & nm = upm.m();
SASSERT(upm.degree(f) >= 2);
unsigned n = upm.degree(f)/2;
scoped_numeral f_norm(nm);
for (unsigned i = 0; i < f.size(); ++ i) {
if (!nm.is_zero(f[i])) {
nm.addmul(f_norm, f[i], f[i], f_norm);
}
}
nm.root(f_norm, 2);
scoped_numeral bound(nm);
nm.set(bound, 1);
nm.mul2k(bound, n, bound);
scoped_numeral tmp(nm);
nm.set(tmp, f.back());
nm.abs(tmp);
nm.add(f_norm, tmp, f_norm);
nm.mul(bound, f_norm, bound);
nm.set(tmp, p);
unsigned e;
for (e = 1; nm.le(tmp, bound); e *= 2) {
nm.mul(tmp, tmp, tmp);
}
return e;
}
\brief Given f from Z[x] that is square free, it factors it.
This method also assumes f is primitive.
*/
bool factor_square_free(z_manager & upm, numeral_vector const & f, factors & fs, unsigned k, factor_params const & params) {
TRACE("polynomial::factorization::bughunt",
tout << "sage: f = "; upm.display(tout, f); tout << endl;
tout << "sage: if (not f.is_squarefree()): print 'Error, f is not square-free'" << endl;
tout << "sage: print 'Factoring :', f" << endl;
tout << "sage: print 'Expected factors: ', f.factor()" << endl;
);
numeral_manager & nm = upm.m();
DEBUG_CODE({
scoped_numeral f_cont(nm);
nm.gcd(f.size(), f.data(), f_cont);
SASSERT(f.size() == 0 || nm.is_one(f_cont));
});
scoped_numeral_vector f_pp(nm);
upm.set(f.size(), f.data(), f_pp);
if (!f_pp.empty() && nm.is_neg(f_pp[f_pp.size() - 1])) {
for (unsigned i = 0; i < f_pp.size(); i++)
nm.neg(f_pp[i]);
if (k % 2 == 1) {
scoped_numeral c(nm);
nm.set(c, fs.get_constant());
nm.neg(c);
fs.set_constant(c);
}
}
TRACE("polynomial::factorization::bughunt",
tout << "sage: f_pp = "; upm.display(tout, f_pp); tout << endl;
tout << "sage: if (not (f_pp * 1 == f): print 'Error, content computation wrong'" << endl;
);
scoped_numeral p(nm);
nm.set(p, 2);
zp_manager zp_upm(upm.lim(), nm.m());
zp_upm.set_zp(p);
zp_factors zp_fs(zp_upm);
scoped_numeral zp_fs_p(nm); nm.set(zp_fs_p, 2);
factorization_degree_set degree_set(zp_upm);
prime_iterator prime_it;
scoped_numeral gcd_tmp(nm);
unsigned trials = 0;
TRACE("polynomial::factorization::bughunt", tout << "trials: " << params.m_p_trials << "\n";);
while (trials <= params.m_p_trials) {
upm.checkpoint();
uint64_t next_prime = prime_it.next();
if (next_prime > params.m_max_p) {
fs.push_back(f_pp, k);
return false;
}
nm.set(p, next_prime);
zp_upm.set_zp(p);
nm.gcd(p, f_pp.back(), gcd_tmp);
TRACE("polynomial::factorization::bughunt",
tout << "sage: if (not (gcd(" << nm.to_string(p) << ", " << nm.to_string(f_pp.back()) << ")) == " <<
nm.to_string(gcd_tmp) << "): print 'Error, wrong gcd'" << endl;
);
if (!nm.is_one(gcd_tmp)) {
continue;
}
scoped_numeral_vector f_pp_zp(nm);
to_zp_manager(zp_upm, f_pp, f_pp_zp);
TRACE("polynomial::factorization::bughunt",
tout << "sage: Rp.<x_p> = GF(" << nm.to_string(p) << ")['x_p']"; tout << endl;
tout << "sage: f_pp_zp = "; zp_upm.display(tout, f_pp_zp, "x_p"); tout << endl;
);
if (!zp_upm.is_square_free(f_pp_zp.size(), f_pp_zp.data()))
continue;
zp_upm.mk_monic(f_pp_zp.size(), f_pp_zp.data());
zp_factors current_fs(zp_upm);
bool factored = zp_factor_square_free(zp_upm, f_pp_zp, current_fs);
if (!factored) {
fs.push_back(f_pp, k);
return true;
}
factorization_degree_set current_degree_set(current_fs);
if (degree_set.max_degree() == 0) {
degree_set.swap(current_degree_set);
}
else {
degree_set.intersect(current_degree_set);
}
if (degree_set.is_trivial()) {
fs.push_back(f_pp, k);
return true;
}
trials ++;
if (zp_fs.distinct_factors() == 0 || zp_fs.total_factors() > current_fs.total_factors()) {
zp_fs.swap(current_fs);
nm.set(zp_fs_p, p);
TRACE("polynomial::factorization::bughunt",
tout << "best zp factorization (Z_" << nm.to_string(zp_fs_p) << "): ";
tout << zp_fs << endl;
tout << "best degree set: "; degree_set.display(tout); tout << endl;
);
}
}
#ifndef _EXTERNAL_RELEASE
IF_VERBOSE(FACTOR_VERBOSE_LVL, verbose_stream() << "(polynomial-factorization :at GF_" << nm.to_string(zp_upm.p()) << ")" << std::endl;);
#endif
zp_upm.set_zp(zp_fs_p);
TRACE("polynomial::factorization::bughunt",
tout << "best zp factorization (Z_" << nm.to_string(zp_fs_p) << "): " << zp_fs << endl;
tout << "best degree set: "; degree_set.display(tout); tout << endl;
);
unsigned e = mignotte_bound(upm, f_pp, zp_fs_p);
TRACE("polynomial::factorization::bughunt",
tout << "out p = " << nm.to_string(zp_fs_p) << ", and we'll work p^e for e = " << e << endl;
);
zp_manager zpe_upm(upm.lim(), nm.m());
zpe_upm.set_zp(zp_fs_p);
zp_numeral_manager & zpe_nm = zpe_upm.m();
zp_factors zpe_fs(zpe_upm);
hensel_lift(upm, f_pp, zp_fs, e, zpe_fs);
#ifndef _EXTERNAL_RELEASE
IF_VERBOSE(FACTOR_VERBOSE_LVL, verbose_stream() << "(polynomial-factorization :num-candidate-factors " << zpe_fs.distinct_factors() << ")" << std::endl;);
#endif
scoped_numeral f_pp_lc(nm);
zpe_nm.set(f_pp_lc, f_pp.back());
upm.mul(f_pp, f_pp_lc);
ufactorization_combination_iterator it(zpe_fs, degree_set);
scoped_numeral_vector trial_factor(nm), trial_factor_quo(nm);
scoped_numeral trial_factor_cont(nm);
TRACE("polynomial::factorization::bughunt",
tout << "STARTING TRIAL DIVISION" << endl;
tout << "zpe_fs" << zpe_fs << endl;
tout << "degree_set = "; degree_set.display(tout); tout << endl;
);
bool result = true;
bool remove = false;
unsigned counter = 0;
while (it.next(remove)) {
upm.checkpoint();
counter++;
if (counter > params.m_max_search_size) {
result = false;
break;
}
bool using_left = it.current_degree() <= zp_fs.get_degree()/2;
if (using_left) {
scoped_numeral tmp(nm);
it.get_left_tail_coeff(f_pp_lc, tmp);
if (!nm.divides(tmp, f_pp[0])) {
remove = false;
continue;
}
it.left(trial_factor);
}
else {
scoped_numeral tmp(nm);
it.get_right_tail_coeff(f_pp_lc, tmp);
if (!nm.divides(tmp, f_pp[0])) {
remove = false;
continue;
}
it.right(trial_factor);
}
zpe_upm.mul(trial_factor, f_pp_lc);
TRACE("polynomial::factorization::bughunt",
tout << "f_pp*lc(f_pp) = "; upm.display(tout, f_pp); tout << endl;
tout << "trial_factor = "; upm.display(tout, trial_factor); tout << endl;
);
bool true_factor = upm.exact_div(f_pp, trial_factor, trial_factor_quo);
TRACE("polynomial::factorization::bughunt",
tout << "trial_factor = "; upm.display(tout, trial_factor); tout << endl;
tout << "trial_factor_quo = "; upm.display(tout, trial_factor_quo); tout << endl;
tout << "result = " << (true_factor ? "true" : "false") << endl;
);
if (true_factor) {
if (!using_left) {
trial_factor.swap(trial_factor_quo);
}
upm.get_primitive_and_content(trial_factor, trial_factor, trial_factor_cont);
fs.push_back(trial_factor, k);
upm.get_primitive_and_content(trial_factor_quo, f_pp, trial_factor_cont);
nm.set(f_pp_lc, f_pp.back());
upm.mul(f_pp, f_pp_lc);
remove = true;
}
else {
remove = false;
}
TRACE("polynomial::factorization::bughunt",
tout << "factors = " << fs << endl;
tout << "f_pp*lc(f_pp) = "; upm.display(tout, f_pp); tout << endl;
tout << "lc(f_pp) = " << f_pp_lc << endl;
);
}
#ifndef _EXTERNAL_RELEASE
IF_VERBOSE(FACTOR_VERBOSE_LVL, verbose_stream() << "(polynomial-factorization :search-size " << counter << ")" << std::endl;);
#endif
if (f_pp.size() > 1) {
upm.div(f_pp, f_pp_lc);
fs.push_back(f_pp, k);
}
else {
SASSERT(f_pp.size() == 1 && nm.is_one(f_pp.back()));
}
return result;
}
bool factor_square_free(z_manager & upm, numeral_vector const & f, factors & fs, factor_params const & params) {
return factor_square_free(upm, f, fs, 1, params);
}
};