def hensel_step(m, f, g, h, s, t): # step 1 zx = ZX() e = zx.add(f, zx.add_inv(zx.mul(g, h))) e = zx.mod_elem(e, m * m) q = zx.mul(s, e) quot, rem = zx.extended_synthetic_division(q.coefs, h.coefs) q = zx.mod_elem(Polynomial(quot, ZZ()), m * m) r = zx.mod_elem(Polynomial(rem, ZZ()), m * m) g_star = zx.add(g, zx.mul(t, e)) g_star = zx.add(g_star, zx.mul(q, g)) g_star = zx.mod_elem(g_star, m * m) h_star = zx.mod_elem(zx.add(h, r), m * m) # step 2 b = zx.mul(s, g_star) b = zx.add(b, zx.mul(t, h_star)) b = zx.add(b, zx.add_inv(zx.mul_id())) b = zx.mod_elem(b, m * m) c = zx.mul(s, b) quot, rem = zx.extended_synthetic_division(c.coefs, h_star.coefs) c = zx.mod_elem(Polynomial(quot, ZZ()), m * m) d = zx.mod_elem(Polynomial(rem, ZZ()), m * m) s_star = zx.add(s, zx.add_inv(d)) s_star = zx.mod_elem(s_star, m * m) t_star = zx.add(t, zx.add_inv(zx.mul(t, b))) t_star = zx.add(t_star, zx.add_inv(zx.mul(c, g_star))) t_star = zx.mod_elem(t_star, m * m) return g_star, h_star, s_star, t_star
def primitive_part(self, elem): if elem == self.add_id(): return self.add_id() else: # divide all coefficients by common gcd to obtain primitive part return Polynomial( list( map(lambda x: ZZ().div(x, self.mcd_polynomial_coefs(elem)), elem.coefs)), ZZ())
def factorize_prime_power(self): # step 1 if self.n == 1: return [self.f] b = self.f.get_leading_coef() B = ceil(sqrt(self.n + 1) * pow(2, self.n) * self.A * b) C = (self.n + 1)**(2 * self.n) * (self.A**(2 * self.n - 1)) gamma = ceil(2 * log2(C)) # step 2 p = ZZ().obtain_random_prime_in_range(3, ceil(2 * gamma * log(gamma))) f_bar = Polynomial(self.f.coefs, Zp(p)) f_bar_der = f_bar.derivative() fx = PolynomialsOverField(Zp(p)) while b % p == 0 or fx.gcd(f_bar, f_bar_der).degree() > 1: p = ZZ().obtain_random_prime_in_range(2, ceil(2 * gamma * log(gamma))) f_bar = Polynomial(self.f.coefs, Zp(p)) f_bar_der = f_bar.derivative() l = ceil(log(2 * B + 1, p)) # step 3 h_i = self.modular_factorization(f_bar, p, fx) # step 4 fact = self.multifactor_hensel_lifting(p, self.f, h_i, l) g_i = [self.symmetric_form(factor, p**l) for factor in fact] # step 5 r = len(g_i) f_star = copy.deepcopy(self.f) T = set(range(r)) s = 1 G = [] # step 6 while 2 * s <= len(T): goto = False combination = list(combinations(T, s)) for subset_tuple in combination: subset = set(subset_tuple) g_star = self.obtain_factors(subset, g_i, b, p**l) h_star = self.obtain_factors(T.difference(subset), g_i, b, p**l) if ZX().onenorm(g_star) * ZX().onenorm(h_star) <= B: T = T.difference(subset) G.append(ZX().primitive_part(g_star)) f_star = ZX().primitive_part(h_star) b = f_star.get_leading_coef() goto = True break if not goto: s += 1 G.append(f_star) return G
def div_mod(self, dividendo, divisor): if len(dividendo) == 2: a, b, c1 = self.obtain_padic_representation(dividendo) else: a, b, c1 = dividendo if len(divisor) == 2: c, d, c2 = self.obtain_padic_representation(divisor) else: c, d, c2 = divisor # |sigma|p < |tau|p if c2 < c1: return divisor, (0, 1, 0) # Otherwise s = (a * d) % self.p**(c2 - c1 + 1) t = (b * c) % self.p**(c2 - c1 + 1) _, t_inv, _ = ZZ().extended_gcd(t, self.p**(c2 - c1 + 1)) quotient = self.symmetric_representation( (s * t_inv) % self.p**(c2 - c1 + 1), self.p**(c2 - c1 + 1)) # nu = sigma - q*tau remainder = self.add( self.obtain_fraction_representation(dividendo), self.add_inv( self.mul(self.obtain_fraction_representation(divisor), (quotient, self.p**(c2 - c1))))) # In case input was in fraction form, output is given in the same format if len(dividendo) == 2: return (quotient, self.p**(c2 - c1)), remainder return (quotient, 1, c1 - c2), self.obtain_padic_representation(remainder)
def extended_synthetic_division(dividend, divisor): dividend.reverse() divisor.reverse() out = list(dividend) # Copy the dividend normalizer = divisor[0] for i in range(len(dividend) - len(divisor) + 1): out[i] = ZZ().div(out[i], normalizer) # for general polynomial division (when polynomials are non-monic), # we need to normalize by dividing the coefficient with the divisor's first coefficient coef = out[i] if coef != ZZ().add_id(): # useless to multiply if coef is 0 for j in range(1, len(divisor)): # in synthetic division, we always skip the first coefficient of the divisor, # because it is only used to normalize the dividend coefficients aux = ZZ().add_inv(ZZ().mul(divisor[j], coef)) out[i + j] = ZZ().add(out[i + j], aux) separator = len(dividend) - len(divisor) + 1 quotient = out[:separator] quotient.reverse() remainder = out[separator:] remainder.reverse() dividend.reverse() divisor.reverse() if len(quotient) == 0: quotient = [ZZ().add_id()] if len(remainder) == 0: remainder = [ZZ().add_id()] return quotient, remainder # return quotient, remainder.
def gcd(self, a, b): c = self.primitive_part(a) d = self.primitive_part(b) if c.degree() < d.degree(): c, d = d, c while d != self.add_id(): lc = c.get_leading_coef() n = c.degree() - d.degree() + 1 aux = Polynomial([lc**n], ZZ()) aux = self.mul(aux, c) r = Polynomial( self.extended_synthetic_division(aux.coefs, d.coefs)[1], ZZ()) c = d d = self.primitive_part(r) gcd_ab = ZZ().gcd(self.mcd_polynomial_coefs(a), self.mcd_polynomial_coefs(b)) return self.mul(Polynomial([gcd_ab], ZZ()), c)
def factorize_big_prime(self): if self.n == 1: return [self.f] b = self.f.get_leading_coef() B = ceil(sqrt(self.n + 1) * pow(2, self.n) * self.A * b) p = ZZ().obtain_random_prime_in_range(2 * B + 1, 4 * B - 1) f_bar = Polynomial(self.f.coefs, Zp(p)) f_bar_der = f_bar.derivative() fx = PolynomialsOverField(Zp(p)) while fx.gcd(f_bar, f_bar_der).degree() > 1: p = ZZ().obtain_random_prime_in_range(2 * B + 1, 4 * B - 1) f_bar = Polynomial(self.f.coefs, Zp(p)) f_bar_der = f_bar.derivative() g_i = self.modular_factorization(f_bar, p, fx) r = len(g_i) f_star = copy.deepcopy(self.f) T = set(range(r)) s = 1 G = [] while 2 * s <= len(T): goto = False combination = list(combinations(T, s)) for subset_tuple in combination: subset = set(subset_tuple) g_star = self.obtain_factors(subset, g_i, b, p) h_star = self.obtain_factors(T.difference(subset), g_i, b, p) if ZX().onenorm(g_star) * ZX().onenorm(h_star) <= B: T = T.difference(subset) G.append(ZX().primitive_part(g_star)) f_star = ZX().primitive_part(h_star) b = f_star.get_leading_coef() goto = True break if not goto: s += 1 G.append(f_star) return G
def pollard(self): ai, bi, xi = 0, 0, self.finite_field.mul_id() a2i, b2i, x2i = 0, 0, self.finite_field.mul_id() while True: xi, ai, bi = self._f(xi), self._h(xi, ai), self._g(xi, bi) x2i, a2i, b2i = self._f(self._f(x2i)), self._h(self._f(x2i), self._h(x2i, a2i)), \ self._g(self._f(x2i), self._g(x2i, b2i)) if xi == x2i: r = bi - b2i if r == 0: return None x = (ZZ().extended_gcd(r, self.p)[1] * (a2i - ai)) % self.p return x
def multifactor_hensel_lifting(self, p, f, fr, l): zx = ZX() r = len(fr) # step 1 if r == 1: # extended gcd in ZZ if f.get_leading_coef() == 1: return [f] pl = p**l _, w, _ = ZZ().extended_gcd(f.get_leading_coef(), pl) return [zx.mod_elem(zx.mul_scalar(f, w), pl)] # step 2 k = floor(r / 2) d = ceil(log2(l)) # step 3 g = reduce(zx.mul, fr[0:k]) g = zx.mul_scalar(g, f.get_leading_coef()) g = zx.mod_elem(g, p) h = reduce(zx.mul, fr[k:r]) h = zx.mod_elem(h, p) # step 4: ZZ/<p> is always a field (namely Zp), since p is prime zpx = PolynomialsOverField(Zp(p)) coef, s, t = zpx.extended_gcd(g, h) s = zpx.mul_scalar(s, Zp(p).mul_inv(coef.coefs[0])) t = zpx.mul_scalar(t, Zp(p).mul_inv(coef.coefs[0])) # step 5 m = p for j in range(d): # step 6 g, h, s, t = self.hensel_step(m, f, g, h, s, t) m **= 2 _k1 = self.multifactor_hensel_lifting(p, g, fr[0:k], l) _k2 = self.multifactor_hensel_lifting(p, h, fr[k:r], l) _k1.extend(_k2) return _k1
def mcd_polynomial_coefs(elem): # gcd not defined for zero values: remove them all before proceeding without_zeros = list(filter(lambda x: x != 0, elem.coefs)) # calculate gcd of all non-zero values in list return ZZ().multiple_gcd(without_zeros)
def mod_elem(f, m): return Polynomial([elem % m for elem in f.coefs], ZZ())
def simplify(elem): a, b = elem if a == 0: return 0, 1 gcd = ZZ().gcd(a, b) return a // gcd, b // gcd
def add_inv(self, elem): lis = list(map(lambda x: ZZ().add_inv(x), elem.coefs)) return Polynomial(lis, ZZ())
def add(self, elem1, elem2): lis = list( map(lambda x1, x2: ZZ().add(x1, x2), elem1.coefs, elem2.coefs)) lis.extend(elem1.coefs[len(lis):]) lis.extend(elem2.coefs[len(lis):]) return Polynomial(lis, ZZ())
def mul(self, elem1, elem2): res = [0] * (elem1.degree() + elem2.degree() + 1) for in1, e1 in enumerate(elem1.coefs): for in2, e2 in enumerate(elem2.coefs): res[in1 + in2] += ZZ().mul(e1, e2) return Polynomial(res, ZZ())
def mul_id(self): return Polynomial([1], ZZ())
elif self.monomial_less_grlex(max_m, elem): max_m = elem return max_m def leading_coef(self): if len(self.coefs) == 0: return self.base_field.add_id() return self.coefs[self.md] def leading_monomial(self): return self.md def leading_term(self): return MultivariablePolynomial({self.md: self.leading_coef()}, self.base_field) def __repr__(self): return str(self.coefs) def __eq__(self, other): return self.coefs == other.coefs if __name__ == "__main__": from specific.ZZ import ZZ dic = {(1, 1, 2): 4, (3, 0, 0): 4, (0, 4, 0): -5, (1, 2, 1): 7} p = MultivariablePolynomial(dic, ZZ()) print("Multidegree:", p.md) print("L.coef:", p.leading_coef()) print("L.monomial:", p.leading_monomial()) print("L.term:", p.leading_term())
_s.append(r) if not _s: return _g else: _g.extend(_s) # check whether f is in <f1, ..., fs>. Theorem 21.28, page 605, MCA def in_ideal(self, f, fs): _g = self.groebner_basis(fs) return self.div(f, fs)[1] == MultivariablePolynomial({}, self.field) if __name__ == "__main__": from specific.ZZ import ZZ dic1 = {(1, 1, 2): 4, (3, 0, 0): 4, (0, 4, 0): -5, (1, 2, 1): 7} p1 = MultivariablePolynomial(dic1, ZZ()) dic2 = {(1, 1, 2): 4, (3, 0, 0): 4, (0, 5, 0): -3, (1, 2, 1): -7} p2 = MultivariablePolynomial(dic2, ZZ()) print("p1 = ", p1) print("p2 = ", p2) print("p1 + p2 = ", MultivariablePolynomialsOverField(ZZ()).add(p1, p2)) print("p1 * p2 = ", MultivariablePolynomialsOverField(ZZ()).mul(p1, p2)) dic3 = {(2, 4, 6, 8): 25} p3 = MultivariablePolynomial(dic3, ZZ()) dic4 = {(2, 2, 3, 2): 5} p4 = MultivariablePolynomial(dic4, ZZ()) print("p3 = ", p3) print("p4 = ", p4) print("p3 / p4 = ", MultivariablePolynomialsOverField(ZZ()).div_term(p3, p4))
def add_id(self): return Polynomial([0], ZZ())
from specific.ZZ import ZZ ZZ = ZZ() print(ZZ.rem(5, 3)) print(ZZ.add_id()) print(ZZ.mul_id()) print(ZZ.mul(7, -2)) print(ZZ.gcd(178, 4)) print(ZZ.gcd(7, -5)) print(ZZ.extended_gcd(15, 25)) print(ZZ.extended_gcd(25, 15)) print(ZZ.extended_gcd(7, 25))
p**l) if ZX().onenorm(g_star) * ZX().onenorm(h_star) <= B: T = T.difference(subset) G.append(ZX().primitive_part(g_star)) f_star = ZX().primitive_part(h_star) b = f_star.get_leading_coef() goto = True break if not goto: s += 1 G.append(f_star) return G if __name__ == "__main__": g = Polynomial([-15, -17, -16, -1, 1], ZZ()) fact = FactorizationZx(g) print(fact.factorize_big_prime()) print(fact.factorize_prime_power()) p_big = Polynomial([-12, -24, -18, -2, 8, 3, 0, 2, 1], ZZ()) # print(FactorizationZx(p_big).factorize_big_prime()) print(FactorizationZx(p_big).factorize_prime_power()) m = 5 f = Polynomial([-1, 0, 0, 0, 1], ZZ()) g = Polynomial([-2, -1, 2, 1], ZZ()) h = Polynomial([-2, 1], ZZ()) s = Polynomial([-2], ZZ()) t = Polynomial([-1, -2, 2], ZZ())
def mul_inv(self, elem): _, d, _ = ZZ().extended_gcd(elem, self.p) return d % self.p
def maxnorm(f): return reduce(lambda x, y: max(abs(x), abs(y)), f.coefs) @staticmethod def onenorm(f): return reduce(lambda x, y: abs(x) + abs(y), f.coefs) # compute f mod m, where m is an element of ZZ @staticmethod def mod_elem(f, m): return Polynomial([elem % m for elem in f.coefs], ZZ()) if __name__ == "__main__": ZX = ZX() _a = Polynomial([1, 2, 1], ZZ()) _b = Polynomial([1, 0, 2], ZZ()) _c = Polynomial([1, 2, 2, 1], ZZ()) _d = Polynomial([27, 9, 18, 0, 9], ZZ()) _e = Polynomial([3, 6, 3], ZZ()) _f = Polynomial([3, 6, 6, 3], ZZ()) _g = Polynomial([1, 0, 1], ZZ()) _h = Polynomial([5, 3], ZZ()) _i = Polynomial([1, 1], ZZ()) _j = Polynomial([1, 2], ZZ()) _k = Polynomial([-5, 3, -7, -2], ZZ()) # print(ZX.extended_synthetic_division(_c.coefs, _a.coefs)) # print(ZX.mcd_polynomial_coefs(_d)) # print(ZX.primitive_part(_d).coefs) print(ZX.gcd(_c, _a).coefs) print(ZX.gcd(_a, _c).coefs)