def recombine(ucoeffs, wcoeffs, f, q, i=0): # Find which representations of coefficients in u and w must be used (O(2^deg(f)) :D) if i >= len(ucoeffs) + len(wcoeffs) - 1: return None, None # index i positive u = Polynomial(ucoeffs, f.var) w = Polynomial(wcoeffs, f.var) if u * w == f: return u, w u_, w_ = recombine(ucoeffs, wcoeffs, f, q, i + 1) if u_ is not None: return u_, w_ if i < len(ucoeffs): temp = ucoeffs[:] temp[i] -= q u_ = Polynomial(temp, f.var) if u_ * w == f: return u_, w return recombine(temp, wcoeffs, f, q, i + 1) else: temp = wcoeffs[:] temp[i - len(ucoeffs)] -= q w_ = Polynomial(temp, f.var) if u * w_ == f: return u, w_ return recombine(ucoeffs, temp, f, q, i + 1)
def at(self, a): if a in self._base_ring: return Polynomial([a @ self._base_ring], self._var) if a not in self: raise ValueError("the element must be a polynomial") coeffs = a.coefficients return Polynomial([ai @ self._base_ring for ai in coeffs], self._var)
def km(f): a = [0, 1] for ai in a: if f(ai) == 0: return Polynomial([-ai, 1], f.var) ms = IZ.divisors(f(a[0]), positive=True) for i in range(1, f.degree // 2 + 1): ms_all = IZ.divisors(f(a[i])) if i > 1: ms = [(*mi[0], mi[1]) for mi in it.product(ms, ms_all)] else: ms = list(it.product(ms, ms_all)) matrix = [ list( it.accumulate([1] + [ai] * (len(a) - 1), lambda x, y: x * y)) for ai in a ] matrix = Matrix(matrix) for m in ms: b = matrix.LUsolve(Matrix(m)) g = Polynomial(b, f.var) q, r = self._divmod_rational(f, g) if b[i] != 0 and r == self.zero: return g, q a.append(-a[-1] if a[-1] > 0 else -a[-1] + 1) return f, None
def divmod(self, a, b, pseudo=True): """ Returns the quotient and remainder of the division between polynomials a and b. :param a: Polynomial - dividend :param b: Polynomial - divisor :param pseudo: bool - whether to do pseudo-division or not (default True): beta^l * a = b * q + r :return: (Polynomial, Polynomial) - quotient and remainder of a/b """ a = a @ self b = b @ self if a.var != b.var: raise ValueError("variables must be the same") if b == self.zero: raise ZeroDivisionError if a.degree < b.degree: return self.zero, a if pseudo: beta = b.coefficients[-1] ell = a.degree - b.degree + 1 a = self.mul(a, beta**ell) quotient, remainder = self.zero, a while remainder.degree >= b.degree: monomial_exponent = remainder.degree - b.degree monomial_zeros = [self.zero for _ in range(monomial_exponent)] monomial_divisor = Polynomial( monomial_zeros + [ self._base_ring.quot(remainder.coefficients[-1], b.coefficients[-1]) ], a.var) quotient = self.add(quotient, monomial_divisor) remainder = self.sub(remainder, self.mul(monomial_divisor, b)) return quotient, remainder
def _divmod_rational(self, a, b): if a.var != b.var: raise ValueError("variables must be the same") if b == self.zero: raise ZeroDivisionError if a.degree < b.degree: return self.zero, a quotient, remainder = Rational(0), a while remainder.degree >= b.degree: monomial_exponent = remainder.degree - b.degree monomial_zeros = [Rational(0) for _ in range(monomial_exponent)] monomial_divisor = Polynomial( monomial_zeros + [Rational(remainder.coefficients[-1], b.coefficients[-1])], a.var) quotient = quotient + monomial_divisor remainder = remainder - monomial_divisor * b return quotient, remainder
def hl(f): if f.degree == 1: return f, None # Initialization original_pol = f p = get_prime(f) field = IF(p)[self._var] f_ = f @ field # dict-like: poly -> count factors = Counter(field.factor(f_, method='ts')) norm = np.linalg.norm(f.coefficients) n = int(np.ceil(np.math.log(2**(f.degree + 1) * norm, p))) # Lifting u = 1 w = 1 for i, (fac, k) in enumerate(factors.items()): if i < len(factors) // 2: u = field.mul(u, self.pow(fac, k)) else: w = field.mul(w, self.pow(fac, k)) # Step 1 lc = f.coefficients[-1] f = self.mul(lc, f) u = field.mul(lc, field.normal_part(u)) w = field.mul(lc, field.normal_part(w)) # Step 2 _, (s, t) = field.bezout(u, w) # Step 3 u = Polynomial(list(u.coefficients)[:-1] + [lc], u.var) w = Polynomial(list(w.coefficients)[:-1] + [lc], w.var) # Dirty fix for finding the correct representation of u and w u_, w_ = recombine(list(u.coefficients), list(w.coefficients), f, p) if u_ is not None: return u_, w_ e = self.sub(f, self.mul(u, w)) modulus = p bound = p**(n + 1) * lc # Step 4 while modulus < bound: # 4.1 Solve sigma * u + tau * w = c mod p # Division was not working c = Polynomial( [ei // modulus for ei in e.coefficients], e.var) sigma_ = field.mul(s, c) tau_ = field.mul(t, c) q, r = field.divmod(sigma_, w) sigma = r tau = field.add(tau_, field.mul(q, u)) # 4.2 Update factors and compute error u = self.add(u, self.mul(tau, modulus)) w = self.add(w, self.mul(sigma, modulus)) modulus *= p u_, w_ = recombine(list(u.coefficients), list(w.coefficients), f, modulus) if u_ is not None: return u_, w_ e = self.sub(f, self.mul(u, w)) # Step 5 return original_pol, None
def factor(self, f, method=None): """ Returns the factorization of the given polynomial f, that is, a set of irreducible polynomials f_1, ..., f_k such that f = f_1 * ... * f_k. Only implemented for integers. One of the following algorithms can be specified: Kronecker’s method or Hensel Lifting. For the latter the polynomial must be square-free. :param f: Polynomial over IZ - polynomial to be factored :param method: str - algorithm used for the factorization; one of 'km' (Kronecker’s method, default) or 'hl' (Hensel Lifting) :return: [Polynomial or int] - irreducible factors of f (ints are generated from the factorization of content(f)) """ from .integers import IntegerRing, IZ if isinstance(self._base_ring, IntegerRing): if method is None: method = 'km' f = f @ self if method == 'km': def km(f): a = [0, 1] for ai in a: if f(ai) == 0: return Polynomial([-ai, 1], f.var) ms = IZ.divisors(f(a[0]), positive=True) for i in range(1, f.degree // 2 + 1): ms_all = IZ.divisors(f(a[i])) if i > 1: ms = [(*mi[0], mi[1]) for mi in it.product(ms, ms_all)] else: ms = list(it.product(ms, ms_all)) matrix = [ list( it.accumulate([1] + [ai] * (len(a) - 1), lambda x, y: x * y)) for ai in a ] matrix = Matrix(matrix) for m in ms: b = matrix.LUsolve(Matrix(m)) g = Polynomial(b, f.var) q, r = self._divmod_rational(f, g) if b[i] != 0 and r == self.zero: return g, q a.append(-a[-1] if a[-1] > 0 else -a[-1] + 1) return f, None c = self.content(f) f = self.primitive_part(f) factors = [] while True: g, q = km(f) factors.append(g) if g == f: if c == 1: return factors return factors + IZ.factor(c) f = Polynomial([int(c) for c in q.coefficients], f.var) if method == 'hl': from .fields import IF def get_prime(g): lc = g.coefficients[-1] bound = lc * 20 if IZ.factor_limit is None or IZ.factor_limit < bound: IZ.factor_limit = bound primes = (p for i, p in enumerate(IZ._factors) if i == p and i not in (0, 1)) for p in primes: if lc % p == 0: continue field = IF(p)[self._var] h = g.derivative() if h @ field == 0: continue # m = g.sylvester(h) # if m.det() % p == 0: # continue return p def recombine(ucoeffs, wcoeffs, f, q, i=0): # Find which representations of coefficients in u and w must be used (O(2^deg(f)) :D) if i >= len(ucoeffs) + len(wcoeffs) - 1: return None, None # index i positive u = Polynomial(ucoeffs, f.var) w = Polynomial(wcoeffs, f.var) if u * w == f: return u, w u_, w_ = recombine(ucoeffs, wcoeffs, f, q, i + 1) if u_ is not None: return u_, w_ if i < len(ucoeffs): temp = ucoeffs[:] temp[i] -= q u_ = Polynomial(temp, f.var) if u_ * w == f: return u_, w return recombine(temp, wcoeffs, f, q, i + 1) else: temp = wcoeffs[:] temp[i - len(ucoeffs)] -= q w_ = Polynomial(temp, f.var) if u * w_ == f: return u, w_ return recombine(ucoeffs, temp, f, q, i + 1) def hl(f): if f.degree == 1: return f, None # Initialization original_pol = f p = get_prime(f) field = IF(p)[self._var] f_ = f @ field # dict-like: poly -> count factors = Counter(field.factor(f_, method='ts')) norm = np.linalg.norm(f.coefficients) n = int(np.ceil(np.math.log(2**(f.degree + 1) * norm, p))) # Lifting u = 1 w = 1 for i, (fac, k) in enumerate(factors.items()): if i < len(factors) // 2: u = field.mul(u, self.pow(fac, k)) else: w = field.mul(w, self.pow(fac, k)) # Step 1 lc = f.coefficients[-1] f = self.mul(lc, f) u = field.mul(lc, field.normal_part(u)) w = field.mul(lc, field.normal_part(w)) # Step 2 _, (s, t) = field.bezout(u, w) # Step 3 u = Polynomial(list(u.coefficients)[:-1] + [lc], u.var) w = Polynomial(list(w.coefficients)[:-1] + [lc], w.var) # Dirty fix for finding the correct representation of u and w u_, w_ = recombine(list(u.coefficients), list(w.coefficients), f, p) if u_ is not None: return u_, w_ e = self.sub(f, self.mul(u, w)) modulus = p bound = p**(n + 1) * lc # Step 4 while modulus < bound: # 4.1 Solve sigma * u + tau * w = c mod p # Division was not working c = Polynomial( [ei // modulus for ei in e.coefficients], e.var) sigma_ = field.mul(s, c) tau_ = field.mul(t, c) q, r = field.divmod(sigma_, w) sigma = r tau = field.add(tau_, field.mul(q, u)) # 4.2 Update factors and compute error u = self.add(u, self.mul(tau, modulus)) w = self.add(w, self.mul(sigma, modulus)) modulus *= p u_, w_ = recombine(list(u.coefficients), list(w.coefficients), f, modulus) if u_ is not None: return u_, w_ e = self.sub(f, self.mul(u, w)) # Step 5 return original_pol, None c = self.content(f) f = self.primitive_part(f) factors = [] remaining = [f] while remaining: g = remaining.pop() a, b = hl(g) if b is not None: if a == self.one: # Completely factored in b factors.append(b) elif b == self.one: # Completely factored in a factors.append(a) else: remaining.extend([a, b]) else: factors.append(a) if c != 1: factors.extend(IZ.factor(c)) return factors raise ValueError(f"cannot factor in {self._base_ring}")