/** @file factor.cpp * * Polynomial factorization (implementation). * * The interface function factor() at the end of this file is defined in the * GiNaC namespace. All other utility functions and classes are defined in an * additional anonymous namespace. * * Factorization starts by doing a square free factorization and making the * coefficients integer. Then, depending on the number of free variables it * proceeds either in dedicated univariate or multivariate factorization code. * * Univariate factorization does a modular factorization via Berlekamp's * algorithm and distinct degree factorization. Hensel lifting is used at the * end. * * Multivariate factorization uses the univariate factorization (applying a * evaluation homomorphism first) and Hensel lifting raises the answer to the * multivariate domain. The Hensel lifting code is completely distinct from the * code used by the univariate factorization. * * Algorithms used can be found in * [Wan] An Improved Multivariate Polynomial Factoring Algorithm, * P.S.Wang, * Mathematics of Computation, Vol. 32, No. 144 (1978) 1215--1231. * [GCL] Algorithms for Computer Algebra, * K.O.Geddes, S.R.Czapor, G.Labahn, * Springer Verlag, 1992. * [Mig] Some Useful Bounds, * M.Mignotte, * In "Computer Algebra, Symbolic and Algebraic Computation" (B.Buchberger et al., eds.), * pp. 259-263, Springer-Verlag, New York, 1982. */ /* * GiNaC Copyright (C) 1999-2018 Johannes Gutenberg University Mainz, Germany * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ //#define DEBUGFACTOR #include "factor.h" #include "ex.h" #include "numeric.h" #include "operators.h" #include "inifcns.h" #include "symbol.h" #include "relational.h" #include "power.h" #include "mul.h" #include "normal.h" #include "add.h" #include #include #include #include #include #ifdef DEBUGFACTOR #include #endif using namespace std; #include using namespace cln; namespace GiNaC { #ifdef DEBUGFACTOR #define DCOUT(str) cout << #str << endl #define DCOUTVAR(var) cout << #var << ": " << var << endl #define DCOUT2(str,var) cout << #str << ": " << var << endl ostream& operator<<(ostream& o, const vector& v) { auto i = v.begin(), end = v.end(); while ( i != end ) { o << *i << " "; ++i; } return o; } static ostream& operator<<(ostream& o, const vector& v) { auto i = v.begin(), end = v.end(); while ( i != end ) { o << *i << "[" << i-v.begin() << "]" << " "; ++i; } return o; } static ostream& operator<<(ostream& o, const vector& v) { auto i = v.begin(), end = v.end(); while ( i != end ) { o << *i << "[" << i-v.begin() << "]" << " "; ++i; } return o; } ostream& operator<<(ostream& o, const vector& v) { for ( size_t i=0; i>& v) { auto i = v.begin(), end = v.end(); while ( i != end ) { o << i-v.begin() << ": " << *i << endl; ++i; } return o; } #else #define DCOUT(str) #define DCOUTVAR(var) #define DCOUT2(str,var) #endif // def DEBUGFACTOR // anonymous namespace to hide all utility functions namespace { //////////////////////////////////////////////////////////////////////////////// // modular univariate polynomial code typedef std::vector umodpoly; typedef std::vector upoly; typedef vector upvec; // COPY FROM UPOLY.HPP // CHANGED size_t -> int !!! template static int degree(const T& p) { return p.size() - 1; } template static typename T::value_type lcoeff(const T& p) { return p[p.size() - 1]; } static bool normalize_in_field(umodpoly& a) { if (a.size() == 0) return true; if ( lcoeff(a) == a[0].ring()->one() ) { return true; } const cln::cl_MI lc_1 = recip(lcoeff(a)); for (std::size_t k = a.size(); k-- != 0; ) a[k] = a[k]*lc_1; return false; } template static void canonicalize(T& p, const typename T::size_type hint = std::numeric_limits::max()) { if (p.empty()) return; std::size_t i = p.size() - 1; // Be fast if the polynomial is already canonicalized if (!zerop(p[i])) return; if (hint < p.size()) i = hint; bool is_zero = false; do { if (!zerop(p[i])) { ++i; break; } if (i == 0) { is_zero = true; break; } --i; } while (true); if (is_zero) { p.clear(); return; } p.erase(p.begin() + i, p.end()); } // END COPY FROM UPOLY.HPP static void expt_pos(umodpoly& a, unsigned int q) { if ( a.empty() ) return; cl_MI zero = a[0].ring()->zero(); int deg = degree(a); a.resize(degree(a)*q+1, zero); for ( int i=deg; i>0; --i ) { a[i*q] = a[i]; a[i] = zero; } } template struct enable_if { typedef T type; }; template struct enable_if { /* empty */ }; template struct uvar_poly_p { static const bool value = false; }; template<> struct uvar_poly_p { static const bool value = true; }; template<> struct uvar_poly_p { static const bool value = true; }; template // Don't define this for anything but univariate polynomials. static typename enable_if::value, T>::type operator+(const T& a, const T& b) { int sa = a.size(); int sb = b.size(); if ( sa >= sb ) { T r(sa); int i = 0; for ( ; i // Don't define this for anything but univariate polynomials. Otherwise // overload resolution might fail (this actually happens when compiling // GiNaC with g++ 3.4). static typename enable_if::value, T>::type operator-(const T& a, const T& b) { int sa = a.size(); int sb = b.size(); if ( sa >= sb ) { T r(sa); int i = 0; for ( ; i degree(a) || (i-j) > degree(b) ) continue; c[i] = c[i] + a[j] * b[i-j]; } } canonicalize(c); return c; } static umodpoly operator*(const umodpoly& a, const umodpoly& b) { umodpoly c; if ( a.empty() || b.empty() ) return c; int n = degree(a) + degree(b); c.resize(n+1, a[0].ring()->zero()); for ( int i=0 ; i<=n; ++i ) { for ( int j=0 ; j<=i; ++j ) { if ( j > degree(a) || (i-j) > degree(b) ) continue; c[i] = c[i] + a[j] * b[i-j]; } } canonicalize(c); return c; } static upoly operator*(const upoly& a, const cl_I& x) { if ( zerop(x) ) { upoly r; return r; } upoly r(a.size()); for ( size_t i=0; i=ldeg; --deg ) { up[deg] = the(ex_to(e.coeff(x, deg)).to_cl_N()); } for ( ; deg>=0; --deg ) { up[deg] = 0; } canonicalize(up); } static void umodpoly_from_upoly(umodpoly& ump, const upoly& e, const cl_modint_ring& R) { int deg = degree(e); ump.resize(deg+1); for ( ; deg>=0; --deg ) { ump[deg] = R->canonhom(e[deg]); } canonicalize(ump); } static void umodpoly_from_ex(umodpoly& ump, const ex& e, const ex& x, const cl_modint_ring& R) { // assert: e is in Z[x] int deg = e.degree(x); ump.resize(deg+1); int ldeg = e.ldegree(x); for ( ; deg>=ldeg; --deg ) { cl_I coeff = the(ex_to(e.coeff(x, deg)).to_cl_N()); ump[deg] = R->canonhom(coeff); } for ( ; deg>=0; --deg ) { ump[deg] = R->zero(); } canonicalize(ump); } #ifdef DEBUGFACTOR static void umodpoly_from_ex(umodpoly& ump, const ex& e, const ex& x, const cl_I& modulus) { umodpoly_from_ex(ump, e, x, find_modint_ring(modulus)); } #endif static ex upoly_to_ex(const upoly& a, const ex& x) { if ( a.empty() ) return 0; ex e; for ( int i=degree(a); i>=0; --i ) { e += numeric(a[i]) * pow(x, i); } return e; } static ex umodpoly_to_ex(const umodpoly& a, const ex& x) { if ( a.empty() ) return 0; cl_modint_ring R = a[0].ring(); cl_I mod = R->modulus; cl_I halfmod = (mod-1) >> 1; ex e; for ( int i=degree(a); i>=0; --i ) { cl_I n = R->retract(a[i]); if ( n > halfmod ) { e += numeric(n-mod) * pow(x, i); } else { e += numeric(n) * pow(x, i); } } return e; } static upoly umodpoly_to_upoly(const umodpoly& a) { upoly e(a.size()); if ( a.empty() ) return e; cl_modint_ring R = a[0].ring(); cl_I mod = R->modulus; cl_I halfmod = (mod-1) >> 1; for ( int i=degree(a); i>=0; --i ) { cl_I n = R->retract(a[i]); if ( n > halfmod ) { e[i] = n-mod; } else { e[i] = n; } } return e; } static umodpoly umodpoly_to_umodpoly(const umodpoly& a, const cl_modint_ring& R, unsigned int m) { umodpoly e; if ( a.empty() ) return e; cl_modint_ring oldR = a[0].ring(); size_t sa = a.size(); e.resize(sa+m, R->zero()); for ( size_t i=0; icanonhom(oldR->retract(a[i])); } canonicalize(e); return e; } /** Divides all coefficients of the polynomial a by the integer x. * All coefficients are supposed to be divisible by x. If they are not, the * the cast will raise an exception. * * @param[in,out] a polynomial of which the coefficients will be reduced by x * @param[in] x integer that divides the coefficients */ static void reduce_coeff(umodpoly& a, const cl_I& x) { if ( a.empty() ) return; cl_modint_ring R = a[0].ring(); for (auto & i : a) { // cln cannot perform this division in the modular field cl_I c = R->retract(i); i = cl_MI(R, the(c / x)); } } /** Calculates remainder of a/b. * Assertion: a and b not empty. * * @param[in] a polynomial dividend * @param[in] b polynomial divisor * @param[out] r polynomial remainder */ static void rem(const umodpoly& a, const umodpoly& b, umodpoly& r) { int k, n; n = degree(b); k = degree(a) - n; r = a; if ( k < 0 ) return; do { cl_MI qk = div(r[n+k], b[n]); if ( !zerop(qk) ) { for ( int i=0; izero()); canonicalize(r); } /** Calculates quotient of a/b. * Assertion: a and b not empty. * * @param[in] a polynomial dividend * @param[in] b polynomial divisor * @param[out] q polynomial quotient */ static void div(const umodpoly& a, const umodpoly& b, umodpoly& q) { int k, n; n = degree(b); k = degree(a) - n; q.clear(); if ( k < 0 ) return; umodpoly r = a; q.resize(k+1, a[0].ring()->zero()); do { cl_MI qk = div(r[n+k], b[n]); if ( !zerop(qk) ) { q[k] = qk; for ( int i=0; izero()); do { cl_MI qk = div(r[n+k], b[n]); if ( !zerop(qk) ) { q[k] = qk; for ( int i=0; izero()); canonicalize(r); canonicalize(q); } /** Calculates the GCD of polynomial a and b. * * @param[in] a polynomial * @param[in] b polynomial * @param[out] c GCD */ static void gcd(const umodpoly& a, const umodpoly& b, umodpoly& c) { if ( degree(a) < degree(b) ) return gcd(b, a, c); c = a; normalize_in_field(c); umodpoly d = b; normalize_in_field(d); umodpoly r; while ( !d.empty() ) { rem(c, d, r); c = d; d = r; } normalize_in_field(c); } /** Calculates the derivative of the polynomial a. * * @param[in] a polynomial of which to take the derivative * @param[out] d result/derivative */ static void deriv(const umodpoly& a, umodpoly& d) { d.clear(); if ( a.size() <= 1 ) return; d.insert(d.begin(), a.begin()+1, a.end()); int max = d.size(); for ( int i=1; ione() ); } static bool equal_one(const umodpoly& a) { return ( a.size() == 1 && a[0] == a[0].ring()->one() ); } /** Returns true if polynomial a is square free. * * @param[in] a polynomial to check * @return true if polynomial is square free, false otherwise */ static bool squarefree(const umodpoly& a) { umodpoly b; deriv(a, b); if ( b.empty() ) { return false; } umodpoly c; gcd(a, b, c); return equal_one(c); } // END modular univariate polynomial code //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // modular matrix typedef vector mvec; class modular_matrix { #ifdef DEBUGFACTOR friend ostream& operator<<(ostream& o, const modular_matrix& m); #endif public: modular_matrix(size_t r_, size_t c_, const cl_MI& init) : r(r_), c(c_) { m.resize(c*r, init); } size_t rowsize() const { return r; } size_t colsize() const { return c; } cl_MI& operator()(size_t row, size_t col) { return m[row*c + col]; } cl_MI operator()(size_t row, size_t col) const { return m[row*c + col]; } void mul_col(size_t col, const cl_MI x) { for ( size_t rc=0; rc& newrow) { for (std::size_t i2 = 0; i2 < newrow.size(); ++i2) { std::size_t i1 = row*c + i2; m[i1] = newrow[i2]; } } mvec::const_iterator row_begin(size_t row) const { return m.begin()+row*c; } mvec::const_iterator row_end(size_t row) const { return m.begin()+row*c+r; } private: size_t r, c; mvec m; }; #ifdef DEBUGFACTOR modular_matrix operator*(const modular_matrix& m1, const modular_matrix& m2) { const unsigned int r = m1.rowsize(); const unsigned int c = m2.colsize(); modular_matrix o(r,c,m1(0,0)); for ( size_t i=0; iretract(m(i,j)) << ","; } o << R->retract(m(i,m.colsize()-1)) << "}"; if ( i != m.rowsize()-1 ) { o << ","; } } o << "}"; return o; } #endif // def DEBUGFACTOR // END modular matrix //////////////////////////////////////////////////////////////////////////////// /** Calculates the Q matrix for a polynomial. Used by Berlekamp's algorithm. * * @param[in] a_ modular polynomial * @param[out] Q Q matrix */ static void q_matrix(const umodpoly& a_, modular_matrix& Q) { umodpoly a = a_; normalize_in_field(a); int n = degree(a); unsigned int q = cl_I_to_uint(a[0].ring()->modulus); umodpoly r(n, a[0].ring()->zero()); r[0] = a[0].ring()->one(); Q.set_row(0, r); unsigned int max = (n-1) * q; for ( size_t m=1; m<=max; ++m ) { cl_MI rn_1 = r.back(); for ( size_t i=n-1; i>0; --i ) { r[i] = r[i-1] - (rn_1 * a[i]); } r[0] = -rn_1 * a[0]; if ( (m % q) == 0 ) { Q.set_row(m/q, r); } } } /** Determine the nullspace of a matrix M-1. * * @param[in,out] M matrix, will be modified * @param[out] basis calculated nullspace of M-1 */ static void nullspace(modular_matrix& M, vector& basis) { const size_t n = M.rowsize(); const cl_MI one = M(0,0).ring()->one(); for ( size_t i=0; i r ) { M.switch_col(cc, r); } break; } } if ( cc < n ) { M.mul_col(r, recip(M(r,r))); for ( cc=0; ccone()); // find nullspace of Q matrix modular_matrix Q(degree(a), degree(a), R->zero()); q_matrix(a, Q); vector nu; nullspace(Q, nu); const unsigned int k = nu.size(); if ( k == 1 ) { // irreducible return; } list factors = {a}; unsigned int size = 1; unsigned int r = 1; unsigned int q = cl_I_to_uint(R->modulus); list::iterator u = factors.begin(); // calculate all gcd's while ( true ) { for ( unsigned int s=0; s exponent 1/prime * @param[in] ap resulting polynomial */ static void expt_1_over_p(const umodpoly& a, unsigned int prime, umodpoly& ap) { size_t newdeg = degree(a)/prime; ap.resize(newdeg+1); ap[0] = a[0]; for ( size_t i=1; i<=newdeg; ++i ) { ap[i] = a[i*prime]; } } /** Modular square free factorization. * * @param[in] a polynomial * @param[out] factors modular factors * @param[out] mult corresponding multiplicities (exponents) */ static void modsqrfree(const umodpoly& a, upvec& factors, vector& mult) { const unsigned int prime = cl_I_to_uint(a[0].ring()->modulus); int i = 1; umodpoly b; deriv(a, b); if ( b.size() ) { umodpoly c; gcd(a, b, c); umodpoly w; div(a, c, w); while ( unequal_one(w) ) { umodpoly y; gcd(w, c, y); umodpoly z; div(w, y, z); factors.push_back(z); mult.push_back(i); ++i; w = y; umodpoly buf; div(c, y, buf); c = buf; } if ( unequal_one(c) ) { umodpoly cp; expt_1_over_p(c, prime, cp); size_t previ = mult.size(); modsqrfree(cp, factors, mult); for ( size_t i=previ; i& degrees, upvec& ddfactors) { umodpoly a = a_; cl_modint_ring R = a[0].ring(); int q = cl_I_to_int(R->modulus); int nhalf = degree(a)/2; int i = 1; umodpoly w(2); w[0] = R->zero(); w[1] = R->one(); umodpoly x = w; while ( i <= nhalf ) { expt_pos(w, q); umodpoly buf; rem(w, a, buf); w = buf; umodpoly wx = w - x; gcd(a, wx, buf); if ( unequal_one(buf) ) { degrees.push_back(i); ddfactors.push_back(buf); } if ( unequal_one(buf) ) { umodpoly buf2; div(a, buf, buf2); a = buf2; nhalf = degree(a)/2; rem(w, a, buf); w = buf; } ++i; } if ( unequal_one(a) ) { degrees.push_back(degree(a)); ddfactors.push_back(a); } } /** Modular same degree factorization. * Same degree factorization is a kind of misnomer. It performs distinct degree * factorization, but instead of using the Cantor-Zassenhaus algorithm it * (sub-optimally) uses Berlekamp's algorithm for the factors of the same * degree. * * @param[in] a modular polynomial * @param[out] upv vector containing modular factors. if upv was not empty the * new elements are added at the end */ static void same_degree_factor(const umodpoly& a, upvec& upv) { cl_modint_ring R = a[0].ring(); vector degrees; upvec ddfactors; distinct_degree_factor(a, degrees, ddfactors); for ( size_t i=0; ione()); umodpoly c = a; normalize_in_field(c); umodpoly d = b; normalize_in_field(d); s = one; t.clear(); umodpoly d1; umodpoly d2 = one; umodpoly q; while ( true ) { div(c, d, q); umodpoly r = c - q * d; umodpoly r1 = s - q * d1; umodpoly r2 = t - q * d2; c = d; s = d1; t = d2; if ( r.empty() ) break; d = r; d1 = r1; d2 = r2; } cl_MI fac = recip(lcoeff(a) * lcoeff(c)); for (auto & i : s) { i = i * fac; } canonicalize(s); fac = recip(lcoeff(b) * lcoeff(c)); for (auto & i : t) { i = i * fac; } canonicalize(t); } /** Replaces the leading coefficient in a polynomial by a given number. * * @param[in] poly polynomial to change * @param[in] lc new leading coefficient * @return changed polynomial */ static upoly replace_lc(const upoly& poly, const cl_I& lc) { if ( poly.empty() ) return poly; upoly r = poly; r.back() = lc; return r; } /** Calculates the bound for the modulus. * See [Mig]. */ static inline cl_I calc_bound(const ex& a, const ex& x, int maxdeg) { cl_I maxcoeff = 0; cl_R coeff = 0; for ( int i=a.degree(x); i>=a.ldegree(x); --i ) { cl_I aa = abs(the(ex_to(a.coeff(x, i)).to_cl_N())); if ( aa > maxcoeff ) maxcoeff = aa; coeff = coeff + square(aa); } cl_I coeffnorm = ceiling1(the(cln::sqrt(coeff))); cl_I B = coeffnorm * expt_pos(cl_I(2), cl_I(maxdeg)); return ( B > maxcoeff ) ? B : maxcoeff; } /** Calculates the bound for the modulus. * See [Mig]. */ static inline cl_I calc_bound(const upoly& a, int maxdeg) { cl_I maxcoeff = 0; cl_R coeff = 0; for ( int i=degree(a); i>=0; --i ) { cl_I aa = abs(a[i]); if ( aa > maxcoeff ) maxcoeff = aa; coeff = coeff + square(aa); } cl_I coeffnorm = ceiling1(the(cln::sqrt(coeff))); cl_I B = coeffnorm * expt_pos(cl_I(2), cl_I(maxdeg)); return ( B > maxcoeff ) ? B : maxcoeff; } /** Hensel lifting as used by factor_univariate(). * * The implementation follows the algorithm in chapter 6 of [GCL]. * * @param[in] a_ primitive univariate polynomials * @param[in] p prime number that does not divide lcoeff(a) * @param[in] u1_ modular factor of a (mod p) * @param[in] w1_ modular factor of a (mod p), relatively prime to u1_, * fulfilling u1_*w1_ == a mod p * @param[out] u lifted factor * @param[out] w lifted factor, u*w = a */ static void hensel_univar(const upoly& a_, unsigned int p, const umodpoly& u1_, const umodpoly& w1_, upoly& u, upoly& w) { upoly a = a_; const cl_modint_ring& R = u1_[0].ring(); // calc bound B int maxdeg = (degree(u1_) > degree(w1_)) ? degree(u1_) : degree(w1_); cl_I maxmodulus = 2*calc_bound(a, maxdeg); // step 1 cl_I alpha = lcoeff(a); a = a * alpha; umodpoly nu1 = u1_; normalize_in_field(nu1); umodpoly nw1 = w1_; normalize_in_field(nw1); upoly phi; phi = umodpoly_to_upoly(nu1) * alpha; umodpoly u1; umodpoly_from_upoly(u1, phi, R); phi = umodpoly_to_upoly(nw1) * alpha; umodpoly w1; umodpoly_from_upoly(w1, phi, R); // step 2 umodpoly s; umodpoly t; exteuclid(u1, w1, s, t); // step 3 u = replace_lc(umodpoly_to_upoly(u1), alpha); w = replace_lc(umodpoly_to_upoly(w1), alpha); upoly e = a - u * w; cl_I modulus = p; // step 4 while ( !e.empty() && modulus < maxmodulus ) { upoly c = e / modulus; phi = umodpoly_to_upoly(s) * c; umodpoly sigmatilde; umodpoly_from_upoly(sigmatilde, phi, R); phi = umodpoly_to_upoly(t) * c; umodpoly tautilde; umodpoly_from_upoly(tautilde, phi, R); umodpoly r, q; remdiv(sigmatilde, w1, r, q); umodpoly sigma = r; phi = umodpoly_to_upoly(tautilde) + umodpoly_to_upoly(q) * umodpoly_to_upoly(u1); umodpoly tau; umodpoly_from_upoly(tau, phi, R); u = u + umodpoly_to_upoly(tau) * modulus; w = w + umodpoly_to_upoly(sigma) * modulus; e = a - u * w; modulus = modulus * p; } // step 5 if ( e.empty() ) { cl_I g = u[0]; for ( size_t i=1; i primes; if (primes.empty()) { primes = {3, 5, 7}; } if ( p >= primes.back() ) { unsigned int candidate = primes.back() + 2; while ( true ) { size_t n = primes.size()/2; for ( size_t i=0; i p) break; } return candidate; } for (auto & it : primes) { if ( it > p ) { return it; } } throw logic_error("next_prime: should not reach this point!"); } /** Manages the splitting a vector of of modular factors into two partitions. */ class factor_partition { public: /** Takes the vector of modular factors and initializes the first partition */ factor_partition(const upvec& factors_) : factors(factors_) { n = factors.size(); k.resize(n, 0); k[0] = 1; cache.resize(n-1); one.resize(1, factors.front()[0].ring()->one()); len = 1; last = 0; split(); } int operator[](size_t i) const { return k[i]; } size_t size() const { return n; } size_t size_left() const { return n-len; } size_t size_right() const { return len; } /** Initializes the next partition. Returns true, if there is one, false otherwise. */ bool next() { if ( last == n-1 ) { int rem = len - 1; int p = last - 1; while ( rem ) { if ( k[p] ) { --rem; --p; continue; } last = p - 1; while ( k[last] == 0 ) { --last; } if ( last == 0 && n == 2*len ) return false; k[last++] = 0; for ( size_t i=0; i<=len-rem; ++i ) { k[last] = 1; ++last; } fill(k.begin()+last, k.end(), 0); --last; split(); return true; } last = len; ++len; if ( len > n/2 ) return false; fill(k.begin(), k.begin()+len, 1); fill(k.begin()+len+1, k.end(), 0); } else { k[last++] = 0; k[last] = 1; } split(); return true; } /** Get first partition */ umodpoly& left() { return lr[0]; } /** Get second partition */ umodpoly& right() { return lr[1]; } private: void split_cached() { size_t i = 0; do { size_t pos = i; int group = k[i++]; size_t d = 0; while ( i < n && k[i] == group ) { ++d; ++i; } if ( d ) { if ( cache[pos].size() >= d ) { lr[group] = lr[group] * cache[pos][d-1]; } else { if ( cache[pos].size() == 0 ) { cache[pos].push_back(factors[pos] * factors[pos+1]); } size_t j = pos + cache[pos].size() + 1; d -= cache[pos].size(); while ( d ) { umodpoly buf = cache[pos].back() * factors[j]; cache[pos].push_back(buf); --d; ++j; } lr[group] = lr[group] * cache[pos].back(); } } else { lr[group] = lr[group] * factors[pos]; } } while ( i < n ); } void split() { lr[0] = one; lr[1] = one; if ( n > 6 ) { split_cached(); } else { for ( size_t i=0; i> cache; upvec factors; umodpoly one; size_t n; size_t len; size_t last; vector k; }; /** Contains a pair of univariate polynomial and its modular factors. * Used by factor_univariate(). */ struct ModFactors { upoly poly; upvec factors; }; /** Univariate polynomial factorization. * * Modular factorization is tried for several primes to minimize the number of * modular factors. Then, Hensel lifting is performed. * * @param[in] poly expanded square free univariate polynomial * @param[in] x symbol * @param[in,out] prime prime number to start trying modular factorization with, * output value is the prime number actually used */ static ex factor_univariate(const ex& poly, const ex& x, unsigned int& prime) { ex unit, cont, prim_ex; poly.unitcontprim(x, unit, cont, prim_ex); upoly prim; upoly_from_ex(prim, prim_ex, x); // determine proper prime and minimize number of modular factors prime = 3; unsigned int lastp = prime; cl_modint_ring R; unsigned int trials = 0; unsigned int minfactors = 0; const numeric& cont_n = ex_to(cont); cl_I i_cont; if (cont_n.is_integer()) { i_cont = the(cont_n.to_cl_N()); } else { // poly \in Q[x] => poly = q ipoly, ipoly \in Z[x], q \in Q // factor(poly) \equiv q factor(ipoly) i_cont = cl_I(1); } cl_I lc = lcoeff(prim)*i_cont; upvec factors; while ( trials < 2 ) { umodpoly modpoly; while ( true ) { prime = next_prime(prime); if ( !zerop(rem(lc, prime)) ) { R = find_modint_ring(prime); umodpoly_from_upoly(modpoly, prim, R); if ( squarefree(modpoly) ) break; } } // do modular factorization upvec trialfactors; factor_modular(modpoly, trialfactors); if ( trialfactors.size() <= 1 ) { // irreducible for sure return poly; } if ( minfactors == 0 || trialfactors.size() < minfactors ) { factors = trialfactors; minfactors = trialfactors.size(); lastp = prime; trials = 1; } else { ++trials; } } prime = lastp; R = find_modint_ring(prime); // lift all factor combinations stack tocheck; ModFactors mf; mf.poly = prim; mf.factors = factors; tocheck.push(mf); upoly f1, f2; ex result = 1; while ( tocheck.size() ) { const size_t n = tocheck.top().factors.size(); factor_partition part(tocheck.top().factors); while ( true ) { // call Hensel lifting hensel_univar(tocheck.top().poly, prime, part.left(), part.right(), f1, f2); if ( !f1.empty() ) { // successful, update the stack and the result if ( part.size_left() == 1 ) { if ( part.size_right() == 1 ) { result *= upoly_to_ex(f1, x) * upoly_to_ex(f2, x); tocheck.pop(); break; } result *= upoly_to_ex(f1, x); tocheck.top().poly = f2; for ( size_t i=0; i==). */ struct EvalPoint { ex x; int evalpoint; }; #ifdef DEBUGFACTOR ostream& operator<<(ostream& o, const vector& v) { for ( size_t i=0; i multivar_diophant(const vector& a_, const ex& x, const ex& c, const vector& I, unsigned int d, unsigned int p, unsigned int k); /** Utility function for multivariate Hensel lifting. * * Solves the equation * s_1*b_1 + ... + s_r*b_r == 1 mod p^k * with deg(s_i) < deg(a_i) * and with given b_1 = a_1 * ... * a_{i-1} * a_{i+1} * ... * a_r * * The implementation follows the algorithm in chapter 6 of [GCL]. * * @param[in] a vector of modular univariate polynomials * @param[in] x symbol * @param[in] p prime number * @param[in] k p^k is modulus * @return vector of polynomials (s_i) */ static upvec multiterm_eea_lift(const upvec& a, const ex& x, unsigned int p, unsigned int k) { const size_t r = a.size(); cl_modint_ring R = find_modint_ring(expt_pos(cl_I(p),k)); upvec q(r-1); q[r-2] = a[r-1]; for ( size_t j=r-2; j>=1; --j ) { q[j-1] = a[j] * q[j]; } umodpoly beta(1, R->one()); upvec s; for ( size_t j=1; j mdarg(2); mdarg[0] = umodpoly_to_ex(q[j-1], x); mdarg[1] = umodpoly_to_ex(a[j-1], x); vector empty; vector exsigma = multivar_diophant(mdarg, x, umodpoly_to_ex(beta, x), empty, 0, p, k); umodpoly sigma1; umodpoly_from_ex(sigma1, exsigma[0], x, R); umodpoly sigma2; umodpoly_from_ex(sigma2, exsigma[1], x, R); beta = sigma1; s.push_back(sigma2); } s.push_back(beta); return s; } /** Changes the modulus of a modular polynomial. Used by eea_lift(). * * @param[in] R new modular ring * @param[in,out] a polynomial to change (in situ) */ static void change_modulus(const cl_modint_ring& R, umodpoly& a) { if ( a.empty() ) return; cl_modint_ring oldR = a[0].ring(); for (auto & i : a) { i = R->canonhom(oldR->retract(i)); } canonicalize(a); } /** Utility function for multivariate Hensel lifting. * * Solves s*a + t*b == 1 mod p^k given a,b. * * The implementation follows the algorithm in chapter 6 of [GCL]. * * @param[in] a polynomial * @param[in] b polynomial * @param[in] x symbol * @param[in] p prime number * @param[in] k p^k is modulus * @param[out] s_ output polynomial * @param[out] t_ output polynomial */ static void eea_lift(const umodpoly& a, const umodpoly& b, const ex& x, unsigned int p, unsigned int k, umodpoly& s_, umodpoly& t_) { cl_modint_ring R = find_modint_ring(p); umodpoly amod = a; change_modulus(R, amod); umodpoly bmod = b; change_modulus(R, bmod); umodpoly smod; umodpoly tmod; exteuclid(amod, bmod, smod, tmod); cl_modint_ring Rpk = find_modint_ring(expt_pos(cl_I(p),k)); umodpoly s = smod; change_modulus(Rpk, s); umodpoly t = tmod; change_modulus(Rpk, t); cl_I modulus(p); umodpoly one(1, Rpk->one()); for ( size_t j=1; j 2 ) { upvec s = multiterm_eea_lift(a, x, p, k); for ( size_t j=0; j(e) || is_a(e) ) { return e.map(*this); } else if ( is_a(e) ) { numeric mod(R->modulus); numeric halfmod = (mod-1)/2; cl_MI emod = R->canonhom(the(ex_to(e).to_cl_N())); numeric n(R->retract(emod)); if ( n > halfmod ) { return n-mod; } else { return n; } } return e; } }; /** Helps mimicking modular multivariate polynomial arithmetic. * * @param e expression of which to make the coefficients equal to their value * in the modular ring R (symmetric representation) * @param R modular ring * @return resulting expression */ static ex make_modular(const ex& e, const cl_modint_ring& R) { make_modular_map map(R); return map(e.expand()); } /** Utility function for multivariate Hensel lifting. * * Returns the polynomials s_i that fulfill * s_1*b_1 + ... + s_r*b_r == c mod * with given b_1 = a_1 * ... * a_{i-1} * a_{i+1} * ... * a_r * * The implementation follows the algorithm in chapter 6 of [GCL]. * * @param a_ vector of multivariate factors mod p^k * @param x symbol (equiv. x_1 in [GCL]) * @param c polynomial mod p^k * @param I vector of evaluation points * @param d maximum total degree of result * @param p prime number * @param k p^k is modulus * @return vector of polynomials (s_i) */ static vector multivar_diophant(const vector& a_, const ex& x, const ex& c, const vector& I, unsigned int d, unsigned int p, unsigned int k) { vector a = a_; const cl_I modulus = expt_pos(cl_I(p),k); const cl_modint_ring R = find_modint_ring(modulus); const size_t r = a.size(); const size_t nu = I.size() + 1; vector sigma; if ( nu > 1 ) { ex xnu = I.back().x; int alphanu = I.back().evalpoint; ex A = 1; for ( size_t i=0; i b(r); for ( size_t i=0; i anew = a; for ( size_t i=0; i Inew = I; Inew.pop_back(); sigma = multivar_diophant(anew, x, cnew, Inew, d, p, k); ex buf = c; for ( size_t i=0; i(xnu), m).subs(xnu==alphanu) / factorial(m); cm = make_modular(cm, R); if ( !cm.is_zero() ) { vector delta_s = multivar_diophant(anew, x, cm, Inew, d, p, k); ex buf = e; for ( size_t j=0; j(c) ) { nterms = c.nops(); z = c.op(0); } else { nterms = 1; z = c; } for ( size_t i=0; i(ex_to(z.lcoeff(x)).to_cl_N()); upvec delta_s = univar_diophant(amod, x, m, p, k); cl_MI modcm; cl_I poscm = plusp(cm) ? cm : mod(cm, modulus); modcm = cl_MI(R, poscm); for ( size_t j=0; j 1 ) { z = c.op(i+1); } } } for ( size_t i=0; i& I, unsigned int p, const cl_I& l, const upvec& u, const vector& lcU) { const size_t nu = I.size() + 1; const cl_modint_ring R = find_modint_ring(expt_pos(cl_I(p),l)); vector A(nu); A[nu-1] = a; for ( size_t j=nu; j>=2; --j ) { ex x = I[j-2].x; int alpha = I[j-2].evalpoint; A[j-2] = A[j-1].subs(x==alpha); A[j-2] = make_modular(A[j-2], R); } int maxdeg = a.degree(I.front().x); for ( size_t i=1; i maxdeg ) maxdeg = maxdeg2; } const size_t n = u.size(); vector U(n); for ( size_t i=0; i U1 = U; ex monomial = 1; for ( size_t m=0; m newI; for ( size_t i=1; i<=j-2; ++i ) { newI.push_back(I[i-1]); } ex xj = I[j-2].x; int alphaj = I[j-2].evalpoint; size_t deg = A[j-1].degree(xj); for ( size_t k=1; k<=deg; ++k ) { if ( !e.is_zero() ) { monomial *= (xj - alphaj); monomial = expand(monomial); ex dif = e.diff(ex_to(xj), k); ex c = dif.subs(xj==alphaj) / factorial(k); if ( !c.is_zero() ) { vector deltaU = multivar_diophant(U1, x, c, newI, maxdeg, p, cl_I_to_uint(l)); for ( size_t i=0; i {7,x,y+1}. The first * element of the list is always the numeric coefficient. */ static ex put_factors_into_lst(const ex& e) { lst result; if ( is_a(e) ) { result.append(e); return result; } if ( is_a(e) ) { result.append(1); result.append(e.op(0)); return result; } if ( is_a(e) || is_a(e) ) { ex icont(e.integer_content()); result.append(icont); result.append(e/icont); return result; } if ( is_a(e) ) { ex nfac = 1; for ( size_t i=0; i(op) ) { nfac = op; } if ( is_a(op) ) { result.append(op.op(0)); } if ( is_a(op) || is_a(op) ) { result.append(op); } } result.prepend(nfac); return result; } throw runtime_error("put_factors_into_lst: bad term."); } /** Checks a set of numbers for whether each number has a unique prime factor. * * @param[in] f list of numbers to check * @return true: if number set is bad, false: if set is okay (has unique * prime factors) */ static bool checkdivisors(const lst& f) { const int k = f.nops(); numeric q, r; vector d(k); d[0] = ex_to(abs(f.op(0))); for ( int i=1; i(abs(f.op(i))); for ( int j=i-1; j>=0; --j ) { r = d[j]; do { r = gcd(r, q); q = q/r; } while ( r != 1 ); if ( q == 1 ) { return true; } } d[i] = q; } return false; } /** Generates a set of evaluation points for a multivariate polynomial. * The set fulfills the following conditions: * 1. lcoeff(evaluated_polynomial) does not vanish * 2. factors of lcoeff(evaluated_polynomial) have each a unique prime factor * 3. evaluated_polynomial is square free * See [Wan] for more details. * * @param[in] u multivariate polynomial to be factored * @param[in] vn leading coefficient of u in x (x==first symbol in syms) * @param[in] syms set of symbols that appear in u * @param[in] f lst containing the factors of the leading coefficient vn * @param[in,out] modulus integer modulus for random number generation (i.e. |a_i| < modulus) * @param[out] u0 returns the evaluated (univariate) polynomial * @param[out] a returns the valid evaluation points. must have initial size equal * number of symbols-1 before calling generate_set */ static void generate_set(const ex& u, const ex& vn, const exset& syms, const lst& f, numeric& modulus, ex& u0, vector& a) { const ex& x = *syms.begin(); while ( true ) { ++modulus; // generate a set of integers ... u0 = u; ex vna = vn; ex vnatry; exset::const_iterator s = syms.begin(); ++s; for ( size_t i=0; i(x))); if ( !is_a(g) ) { continue; } if ( !is_a(vn) ) { // ... and for which the evaluated factors have each an unique prime factor lst fnum = f; fnum.let_op(0) = fnum.op(0) * u0.content(x); for ( size_t i=1; i(fnum.op(i)) ) { s = syms.begin(); ++s; for ( size_t j=0; j(cont) ) { return factor_sqrfree(cont) * factor_sqrfree(pp); } // factor leading coefficient ex vn = pp.collect(x).lcoeff(x); ex vnlst; if ( is_a(vn) ) { vnlst = lst{vn}; } else { ex vnfactors = factor(vn); vnlst = put_factors_into_lst(vnfactors); } const unsigned int maxtrials = 3; numeric modulus = (vnlst.nops() > 3) ? vnlst.nops() : 3; vector a(syms.size()-1, 0); // try now to factorize until we are successful while ( true ) { unsigned int trialcount = 0; unsigned int prime; int factor_count = 0; int min_factor_count = -1; ex u, delta; ex ufac, ufaclst; // try several evaluation points to reduce the number of factors while ( trialcount < maxtrials ) { // generate a set of valid evaluation points generate_set(pp, vn, syms, ex_to(vnlst), modulus, u, a); ufac = factor_univariate(u, x, prime); ufaclst = put_factors_into_lst(ufac); factor_count = ufaclst.nops()-1; delta = ufaclst.op(0); if ( factor_count <= 1 ) { // irreducible return poly; } if ( min_factor_count < 0 ) { // first time here min_factor_count = factor_count; } else if ( min_factor_count == factor_count ) { // one less to try ++trialcount; } else if ( min_factor_count > factor_count ) { // new minimum, reset trial counter min_factor_count = factor_count; trialcount = 0; } } // determine true leading coefficients for the Hensel lifting vector C(factor_count); if ( is_a(vn) ) { // easy case for ( size_t i=1; i ftilde(vnlst.nops()-1); for ( size_t i=0; i(ft); } // calculate D and C vector used_flag(ftilde.size(), false); vector D(factor_count, 1); if ( delta == 1 ) { for ( int i=0; i(ufaclst.op(i+1).lcoeff(x)); for ( int j=ftilde.size()-1; j>=0; --j ) { int count = 0; while ( irem(prefac, ftilde[j]) == 0 ) { prefac = iquo(prefac, ftilde[j]); ++count; } if ( count ) { used_flag[j] = true; D[i] = D[i] * pow(vnlst.op(j+1), count); } } C[i] = D[i] * prefac; } } else { for ( int i=0; i(ufaclst.op(i+1).lcoeff(x)); for ( int j=ftilde.size()-1; j>=0; --j ) { int count = 0; while ( irem(prefac, ftilde[j]) == 0 ) { prefac = iquo(prefac, ftilde[j]); ++count; } while ( irem(ex_to(delta)*prefac, ftilde[j]) == 0 ) { numeric g = gcd(prefac, ex_to(ftilde[j])); prefac = iquo(prefac, g); delta = delta / (ftilde[j]/g); ufaclst.let_op(i+1) = ufaclst.op(i+1) * (ftilde[j]/g); ++count; } if ( count ) { used_flag[j] = true; D[i] = D[i] * pow(vnlst.op(j+1), count); } } C[i] = D[i] * prefac; } } // check if something went wrong bool some_factor_unused = false; for ( size_t i=0; i epv; s = syms.begin(); ++s; for ( size_t i=0; i maxdeg ) { maxdeg = ufaclst[i].degree(x); } } cl_I B = 2*calc_bound(u, x, maxdeg); cl_I l = 1; cl_I pl = prime; while ( pl < B ) { l = l + 1; pl = pl * prime; } // set up modular factors (mod p^l) cl_modint_ring R = find_modint_ring(expt_pos(cl_I(prime),l)); upvec modfactors(ufaclst.nops()-1); for ( size_t i=1; i(e) ) { syms.insert(e); return e; } return e.map(*this); } }; /** Factorizes a polynomial that is square free. It calls either the univariate * or the multivariate factorization functions. */ static ex factor_sqrfree(const ex& poly) { // determine all symbols in poly find_symbols_map findsymbols; findsymbols(poly); if ( findsymbols.syms.size() == 0 ) { return poly; } if ( findsymbols.syms.size() == 1 ) { // univariate case const ex& x = *(findsymbols.syms.begin()); if ( poly.ldegree(x) > 0 ) { // pull out direct factors int ld = poly.ldegree(x); ex res = factor_univariate(expand(poly/pow(x, ld)), x); return res * pow(x,ld); } else { ex res = factor_univariate(poly, x); return res; } } // multivariate case ex res = factor_multivariate(poly, findsymbols.syms); return res; } /** Map used by factor() when factor_options::all is given to access all * subexpressions and to call factor() on them. */ struct apply_factor_map : public map_function { unsigned options; apply_factor_map(unsigned options_) : options(options_) { } ex operator()(const ex& e) override { if ( e.info(info_flags::polynomial) ) { return factor(e, options); } if ( is_a(e) ) { ex s1, s2; for ( size_t i=0; i void factor_iter(const ex &e, F yield) { if (is_a(e)) { for (const auto &f : e) { if (is_a(f)) { yield(f.op(0), f.op(1)); } else { yield(f, ex(1)); } } } else { if (is_a(e)) { yield(e.op(0), e.op(1)); } else { yield(e, ex(1)); } } } /** This function factorizes a polynomial. It checks the arguments, * tries a square free factorization, and then calls factor_sqrfree * to do the hard work. * * This function expands its argument, so for polynomials with * explicit factors it's better to call it on each one separately * (or use factor() which does just that). */ static ex factor1(const ex& poly, unsigned options) { // check arguments if ( !poly.info(info_flags::polynomial) ) { if ( options & factor_options::all ) { options &= ~factor_options::all; apply_factor_map factor_map(options); return factor_map(poly); } return poly; } // determine all symbols in poly find_symbols_map findsymbols; findsymbols(poly); if ( findsymbols.syms.size() == 0 ) { return poly; } lst syms; for (auto & i : findsymbols.syms ) { syms.append(i); } // make poly square free ex sfpoly = sqrfree(poly.expand(), syms); // factorize the square free components ex res = 1; factor_iter(sfpoly, [&](const ex &f, const ex &k) { if ( is_a(f) ) { res *= pow(factor_sqrfree(f), k); } else { // simple case: (monomial)^exponent res *= pow(f, k); } }); return res; } } // anonymous namespace /** Interface function to the outside world. It uses factor1() * on each of the explicitly present factors of poly. */ ex factor(const ex& poly, unsigned options) { ex result = 1; factor_iter(poly, [&](const ex &f1, const ex &k1) { factor_iter(factor1(f1, options), [&](const ex &f2, const ex &k2) { result *= pow(f2, k1*k2); }); }); return result; } } // namespace GiNaC #ifdef DEBUGFACTOR #include "test.h" #endif