"""Dense univariate polynomials with coefficients in Galois fields. """ from math import ceil as _ceil, sqrt as _sqrt, prod from sympy.core.random import uniform, _randint from sympy.external.gmpy import SYMPY_INTS, MPZ, invert from sympy.polys.polyconfig import query from sympy.polys.polyerrors import ExactQuotientFailed from sympy.polys.polyutils import _sort_factors def gf_crt(U, M, K=None): """ Chinese Remainder Theorem. Given a set of integer residues ``u_0,...,u_n`` and a set of co-prime integer moduli ``m_0,...,m_n``, returns an integer ``u``, such that ``u = u_i mod m_i`` for ``i = ``0,...,n``. Examples ======== Consider a set of residues ``U = [49, 76, 65]`` and a set of moduli ``M = [99, 97, 95]``. Then we have:: >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_crt >>> gf_crt([49, 76, 65], [99, 97, 95], ZZ) 639985 This is the correct result because:: >>> [639985 % m for m in [99, 97, 95]] [49, 76, 65] Note: this is a low-level routine with no error checking. See Also ======== sympy.ntheory.modular.crt : a higher level crt routine sympy.ntheory.modular.solve_congruence """ p = prod(M, start=K.one) v = K.zero for u, m in zip(U, M): e = p // m s, _, _ = K.gcdex(e, m) v += e*(u*s % m) return v % p def gf_crt1(M, K): """ First part of the Chinese Remainder Theorem. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_crt, gf_crt1, gf_crt2 >>> U = [49, 76, 65] >>> M = [99, 97, 95] The following two codes have the same result. >>> gf_crt(U, M, ZZ) 639985 >>> p, E, S = gf_crt1(M, ZZ) >>> gf_crt2(U, M, p, E, S, ZZ) 639985 However, it is faster when we want to fix ``M`` and compute for multiple U, i.e. the following cases: >>> p, E, S = gf_crt1(M, ZZ) >>> Us = [[49, 76, 65], [23, 42, 67]] >>> for U in Us: ... print(gf_crt2(U, M, p, E, S, ZZ)) 639985 236237 See Also ======== sympy.ntheory.modular.crt1 : a higher level crt routine sympy.polys.galoistools.gf_crt sympy.polys.galoistools.gf_crt2 """ E, S = [], [] p = prod(M, start=K.one) for m in M: E.append(p // m) S.append(K.gcdex(E[-1], m)[0] % m) return p, E, S def gf_crt2(U, M, p, E, S, K): """ Second part of the Chinese Remainder Theorem. See ``gf_crt1`` for usage. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_crt2 >>> U = [49, 76, 65] >>> M = [99, 97, 95] >>> p = 912285 >>> E = [9215, 9405, 9603] >>> S = [62, 24, 12] >>> gf_crt2(U, M, p, E, S, ZZ) 639985 See Also ======== sympy.ntheory.modular.crt2 : a higher level crt routine sympy.polys.galoistools.gf_crt sympy.polys.galoistools.gf_crt1 """ v = K.zero for u, m, e, s in zip(U, M, E, S): v += e*(u*s % m) return v % p def gf_int(a, p): """ Coerce ``a mod p`` to an integer in the range ``[-p/2, p/2]``. Examples ======== >>> from sympy.polys.galoistools import gf_int >>> gf_int(2, 7) 2 >>> gf_int(5, 7) -2 """ if a <= p // 2: return a else: return a - p def gf_degree(f): """ Return the leading degree of ``f``. Examples ======== >>> from sympy.polys.galoistools import gf_degree >>> gf_degree([1, 1, 2, 0]) 3 >>> gf_degree([]) -1 """ return len(f) - 1 def gf_LC(f, K): """ Return the leading coefficient of ``f``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_LC >>> gf_LC([3, 0, 1], ZZ) 3 """ if not f: return K.zero else: return f[0] def gf_TC(f, K): """ Return the trailing coefficient of ``f``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_TC >>> gf_TC([3, 0, 1], ZZ) 1 """ if not f: return K.zero else: return f[-1] def gf_strip(f): """ Remove leading zeros from ``f``. Examples ======== >>> from sympy.polys.galoistools import gf_strip >>> gf_strip([0, 0, 0, 3, 0, 1]) [3, 0, 1] """ if not f or f[0]: return f k = 0 for coeff in f: if coeff: break else: k += 1 return f[k:] def gf_trunc(f, p): """ Reduce all coefficients modulo ``p``. Examples ======== >>> from sympy.polys.galoistools import gf_trunc >>> gf_trunc([7, -2, 3], 5) [2, 3, 3] """ return gf_strip([ a % p for a in f ]) def gf_normal(f, p, K): """ Normalize all coefficients in ``K``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_normal >>> gf_normal([5, 10, 21, -3], 5, ZZ) [1, 2] """ return gf_trunc(list(map(K, f)), p) def gf_from_dict(f, p, K): """ Create a ``GF(p)[x]`` polynomial from a dict. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_from_dict >>> gf_from_dict({10: ZZ(4), 4: ZZ(33), 0: ZZ(-1)}, 5, ZZ) [4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4] """ n, h = max(f.keys()), [] if isinstance(n, SYMPY_INTS): for k in range(n, -1, -1): h.append(f.get(k, K.zero) % p) else: (n,) = n for k in range(n, -1, -1): h.append(f.get((k,), K.zero) % p) return gf_trunc(h, p) def gf_to_dict(f, p, symmetric=True): """ Convert a ``GF(p)[x]`` polynomial to a dict. Examples ======== >>> from sympy.polys.galoistools import gf_to_dict >>> gf_to_dict([4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4], 5) {0: -1, 4: -2, 10: -1} >>> gf_to_dict([4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4], 5, symmetric=False) {0: 4, 4: 3, 10: 4} """ n, result = gf_degree(f), {} for k in range(0, n + 1): if symmetric: a = gf_int(f[n - k], p) else: a = f[n - k] if a: result[k] = a return result def gf_from_int_poly(f, p): """ Create a ``GF(p)[x]`` polynomial from ``Z[x]``. Examples ======== >>> from sympy.polys.galoistools import gf_from_int_poly >>> gf_from_int_poly([7, -2, 3], 5) [2, 3, 3] """ return gf_trunc(f, p) def gf_to_int_poly(f, p, symmetric=True): """ Convert a ``GF(p)[x]`` polynomial to ``Z[x]``. Examples ======== >>> from sympy.polys.galoistools import gf_to_int_poly >>> gf_to_int_poly([2, 3, 3], 5) [2, -2, -2] >>> gf_to_int_poly([2, 3, 3], 5, symmetric=False) [2, 3, 3] """ if symmetric: return [ gf_int(c, p) for c in f ] else: return f def gf_neg(f, p, K): """ Negate a polynomial in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_neg >>> gf_neg([3, 2, 1, 0], 5, ZZ) [2, 3, 4, 0] """ return [ -coeff % p for coeff in f ] def gf_add_ground(f, a, p, K): """ Compute ``f + a`` where ``f`` in ``GF(p)[x]`` and ``a`` in ``GF(p)``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_add_ground >>> gf_add_ground([3, 2, 4], 2, 5, ZZ) [3, 2, 1] """ if not f: a = a % p else: a = (f[-1] + a) % p if len(f) > 1: return f[:-1] + [a] if not a: return [] else: return [a] def gf_sub_ground(f, a, p, K): """ Compute ``f - a`` where ``f`` in ``GF(p)[x]`` and ``a`` in ``GF(p)``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_sub_ground >>> gf_sub_ground([3, 2, 4], 2, 5, ZZ) [3, 2, 2] """ if not f: a = -a % p else: a = (f[-1] - a) % p if len(f) > 1: return f[:-1] + [a] if not a: return [] else: return [a] def gf_mul_ground(f, a, p, K): """ Compute ``f * a`` where ``f`` in ``GF(p)[x]`` and ``a`` in ``GF(p)``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_mul_ground >>> gf_mul_ground([3, 2, 4], 2, 5, ZZ) [1, 4, 3] """ if not a: return [] else: return [ (a*b) % p for b in f ] def gf_quo_ground(f, a, p, K): """ Compute ``f/a`` where ``f`` in ``GF(p)[x]`` and ``a`` in ``GF(p)``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_quo_ground >>> gf_quo_ground(ZZ.map([3, 2, 4]), ZZ(2), 5, ZZ) [4, 1, 2] """ return gf_mul_ground(f, K.invert(a, p), p, K) def gf_add(f, g, p, K): """ Add polynomials in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_add >>> gf_add([3, 2, 4], [2, 2, 2], 5, ZZ) [4, 1] """ if not f: return g if not g: return f df = gf_degree(f) dg = gf_degree(g) if df == dg: return gf_strip([ (a + b) % p for a, b in zip(f, g) ]) else: k = abs(df - dg) if df > dg: h, f = f[:k], f[k:] else: h, g = g[:k], g[k:] return h + [ (a + b) % p for a, b in zip(f, g) ] def gf_sub(f, g, p, K): """ Subtract polynomials in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_sub >>> gf_sub([3, 2, 4], [2, 2, 2], 5, ZZ) [1, 0, 2] """ if not g: return f if not f: return gf_neg(g, p, K) df = gf_degree(f) dg = gf_degree(g) if df == dg: return gf_strip([ (a - b) % p for a, b in zip(f, g) ]) else: k = abs(df - dg) if df > dg: h, f = f[:k], f[k:] else: h, g = gf_neg(g[:k], p, K), g[k:] return h + [ (a - b) % p for a, b in zip(f, g) ] def gf_mul(f, g, p, K): """ Multiply polynomials in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_mul >>> gf_mul([3, 2, 4], [2, 2, 2], 5, ZZ) [1, 0, 3, 2, 3] """ df = gf_degree(f) dg = gf_degree(g) dh = df + dg h = [0]*(dh + 1) for i in range(0, dh + 1): coeff = K.zero for j in range(max(0, i - dg), min(i, df) + 1): coeff += f[j]*g[i - j] h[i] = coeff % p return gf_strip(h) def gf_sqr(f, p, K): """ Square polynomials in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_sqr >>> gf_sqr([3, 2, 4], 5, ZZ) [4, 2, 3, 1, 1] """ df = gf_degree(f) dh = 2*df h = [0]*(dh + 1) for i in range(0, dh + 1): coeff = K.zero jmin = max(0, i - df) jmax = min(i, df) n = jmax - jmin + 1 jmax = jmin + n // 2 - 1 for j in range(jmin, jmax + 1): coeff += f[j]*f[i - j] coeff += coeff if n & 1: elem = f[jmax + 1] coeff += elem**2 h[i] = coeff % p return gf_strip(h) def gf_add_mul(f, g, h, p, K): """ Returns ``f + g*h`` where ``f``, ``g``, ``h`` in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_add_mul >>> gf_add_mul([3, 2, 4], [2, 2, 2], [1, 4], 5, ZZ) [2, 3, 2, 2] """ return gf_add(f, gf_mul(g, h, p, K), p, K) def gf_sub_mul(f, g, h, p, K): """ Compute ``f - g*h`` where ``f``, ``g``, ``h`` in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_sub_mul >>> gf_sub_mul([3, 2, 4], [2, 2, 2], [1, 4], 5, ZZ) [3, 3, 2, 1] """ return gf_sub(f, gf_mul(g, h, p, K), p, K) def gf_expand(F, p, K): """ Expand results of :func:`~.factor` in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_expand >>> gf_expand([([3, 2, 4], 1), ([2, 2], 2), ([3, 1], 3)], 5, ZZ) [4, 3, 0, 3, 0, 1, 4, 1] """ if isinstance(F, tuple): lc, F = F else: lc = K.one g = [lc] for f, k in F: f = gf_pow(f, k, p, K) g = gf_mul(g, f, p, K) return g def gf_div(f, g, p, K): """ Division with remainder in ``GF(p)[x]``. Given univariate polynomials ``f`` and ``g`` with coefficients in a finite field with ``p`` elements, returns polynomials ``q`` and ``r`` (quotient and remainder) such that ``f = q*g + r``. Consider polynomials ``x**3 + x + 1`` and ``x**2 + x`` in GF(2):: >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_div, gf_add_mul >>> gf_div(ZZ.map([1, 0, 1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) ([1, 1], [1]) As result we obtained quotient ``x + 1`` and remainder ``1``, thus:: >>> gf_add_mul(ZZ.map([1]), ZZ.map([1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) [1, 0, 1, 1] References ========== .. [1] [Monagan93]_ .. [2] [Gathen99]_ """ df = gf_degree(f) dg = gf_degree(g) if not g: raise ZeroDivisionError("polynomial division") elif df < dg: return [], f inv = K.invert(g[0], p) h, dq, dr = list(f), df - dg, dg - 1 for i in range(0, df + 1): coeff = h[i] for j in range(max(0, dg - i), min(df - i, dr) + 1): coeff -= h[i + j - dg] * g[dg - j] if i <= dq: coeff *= inv h[i] = coeff % p return h[:dq + 1], gf_strip(h[dq + 1:]) def gf_rem(f, g, p, K): """ Compute polynomial remainder in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_rem >>> gf_rem(ZZ.map([1, 0, 1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) [1] """ return gf_div(f, g, p, K)[1] def gf_quo(f, g, p, K): """ Compute exact quotient in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_quo >>> gf_quo(ZZ.map([1, 0, 1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) [1, 1] >>> gf_quo(ZZ.map([1, 0, 3, 2, 3]), ZZ.map([2, 2, 2]), 5, ZZ) [3, 2, 4] """ df = gf_degree(f) dg = gf_degree(g) if not g: raise ZeroDivisionError("polynomial division") elif df < dg: return [] inv = K.invert(g[0], p) h, dq, dr = f[:], df - dg, dg - 1 for i in range(0, dq + 1): coeff = h[i] for j in range(max(0, dg - i), min(df - i, dr) + 1): coeff -= h[i + j - dg] * g[dg - j] h[i] = (coeff * inv) % p return h[:dq + 1] def gf_exquo(f, g, p, K): """ Compute polynomial quotient in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_exquo >>> gf_exquo(ZZ.map([1, 0, 3, 2, 3]), ZZ.map([2, 2, 2]), 5, ZZ) [3, 2, 4] >>> gf_exquo(ZZ.map([1, 0, 1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) Traceback (most recent call last): ... ExactQuotientFailed: [1, 1, 0] does not divide [1, 0, 1, 1] """ q, r = gf_div(f, g, p, K) if not r: return q else: raise ExactQuotientFailed(f, g) def gf_lshift(f, n, K): """ Efficiently multiply ``f`` by ``x**n``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_lshift >>> gf_lshift([3, 2, 4], 4, ZZ) [3, 2, 4, 0, 0, 0, 0] """ if not f: return f else: return f + [K.zero]*n def gf_rshift(f, n, K): """ Efficiently divide ``f`` by ``x**n``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_rshift >>> gf_rshift([1, 2, 3, 4, 0], 3, ZZ) ([1, 2], [3, 4, 0]) """ if not n: return f, [] else: return f[:-n], f[-n:] def gf_pow(f, n, p, K): """ Compute ``f**n`` in ``GF(p)[x]`` using repeated squaring. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_pow >>> gf_pow([3, 2, 4], 3, 5, ZZ) [2, 4, 4, 2, 2, 1, 4] """ if not n: return [K.one] elif n == 1: return f elif n == 2: return gf_sqr(f, p, K) h = [K.one] while True: if n & 1: h = gf_mul(h, f, p, K) n -= 1 n >>= 1 if not n: break f = gf_sqr(f, p, K) return h def gf_frobenius_monomial_base(g, p, K): """ return the list of ``x**(i*p) mod g in Z_p`` for ``i = 0, .., n - 1`` where ``n = gf_degree(g)`` Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_frobenius_monomial_base >>> g = ZZ.map([1, 0, 2, 1]) >>> gf_frobenius_monomial_base(g, 5, ZZ) [[1], [4, 4, 2], [1, 2]] """ n = gf_degree(g) if n == 0: return [] b = [0]*n b[0] = [1] if p < n: for i in range(1, n): mon = gf_lshift(b[i - 1], p, K) b[i] = gf_rem(mon, g, p, K) elif n > 1: b[1] = gf_pow_mod([K.one, K.zero], p, g, p, K) for i in range(2, n): b[i] = gf_mul(b[i - 1], b[1], p, K) b[i] = gf_rem(b[i], g, p, K) return b def gf_frobenius_map(f, g, b, p, K): """ compute gf_pow_mod(f, p, g, p, K) using the Frobenius map Parameters ========== f, g : polynomials in ``GF(p)[x]`` b : frobenius monomial base p : prime number K : domain Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_frobenius_monomial_base, gf_frobenius_map >>> f = ZZ.map([2, 1, 0, 1]) >>> g = ZZ.map([1, 0, 2, 1]) >>> p = 5 >>> b = gf_frobenius_monomial_base(g, p, ZZ) >>> r = gf_frobenius_map(f, g, b, p, ZZ) >>> gf_frobenius_map(f, g, b, p, ZZ) [4, 0, 3] """ m = gf_degree(g) if gf_degree(f) >= m: f = gf_rem(f, g, p, K) if not f: return [] n = gf_degree(f) sf = [f[-1]] for i in range(1, n + 1): v = gf_mul_ground(b[i], f[n - i], p, K) sf = gf_add(sf, v, p, K) return sf def _gf_pow_pnm1d2(f, n, g, b, p, K): """ utility function for ``gf_edf_zassenhaus`` Compute ``f**((p**n - 1) // 2)`` in ``GF(p)[x]/(g)`` ``f**((p**n - 1) // 2) = (f*f**p*...*f**(p**n - 1))**((p - 1) // 2)`` """ f = gf_rem(f, g, p, K) h = f r = f for i in range(1, n): h = gf_frobenius_map(h, g, b, p, K) r = gf_mul(r, h, p, K) r = gf_rem(r, g, p, K) res = gf_pow_mod(r, (p - 1)//2, g, p, K) return res def gf_pow_mod(f, n, g, p, K): """ Compute ``f**n`` in ``GF(p)[x]/(g)`` using repeated squaring. Given polynomials ``f`` and ``g`` in ``GF(p)[x]`` and a non-negative integer ``n``, efficiently computes ``f**n (mod g)`` i.e. the remainder of ``f**n`` from division by ``g``, using the repeated squaring algorithm. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_pow_mod >>> gf_pow_mod(ZZ.map([3, 2, 4]), 3, ZZ.map([1, 1]), 5, ZZ) [] References ========== .. [1] [Gathen99]_ """ if not n: return [K.one] elif n == 1: return gf_rem(f, g, p, K) elif n == 2: return gf_rem(gf_sqr(f, p, K), g, p, K) h = [K.one] while True: if n & 1: h = gf_mul(h, f, p, K) h = gf_rem(h, g, p, K) n -= 1 n >>= 1 if not n: break f = gf_sqr(f, p, K) f = gf_rem(f, g, p, K) return h def gf_gcd(f, g, p, K): """ Euclidean Algorithm in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_gcd >>> gf_gcd(ZZ.map([3, 2, 4]), ZZ.map([2, 2, 3]), 5, ZZ) [1, 3] """ while g: f, g = g, gf_rem(f, g, p, K) return gf_monic(f, p, K)[1] def gf_lcm(f, g, p, K): """ Compute polynomial LCM in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_lcm >>> gf_lcm(ZZ.map([3, 2, 4]), ZZ.map([2, 2, 3]), 5, ZZ) [1, 2, 0, 4] """ if not f or not g: return [] h = gf_quo(gf_mul(f, g, p, K), gf_gcd(f, g, p, K), p, K) return gf_monic(h, p, K)[1] def gf_cofactors(f, g, p, K): """ Compute polynomial GCD and cofactors in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_cofactors >>> gf_cofactors(ZZ.map([3, 2, 4]), ZZ.map([2, 2, 3]), 5, ZZ) ([1, 3], [3, 3], [2, 1]) """ if not f and not g: return ([], [], []) h = gf_gcd(f, g, p, K) return (h, gf_quo(f, h, p, K), gf_quo(g, h, p, K)) def gf_gcdex(f, g, p, K): """ Extended Euclidean Algorithm in ``GF(p)[x]``. Given polynomials ``f`` and ``g`` in ``GF(p)[x]``, computes polynomials ``s``, ``t`` and ``h``, such that ``h = gcd(f, g)`` and ``s*f + t*g = h``. The typical application of EEA is solving polynomial diophantine equations. Consider polynomials ``f = (x + 7) (x + 1)``, ``g = (x + 7) (x**2 + 1)`` in ``GF(11)[x]``. Application of Extended Euclidean Algorithm gives:: >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_gcdex, gf_mul, gf_add >>> s, t, g = gf_gcdex(ZZ.map([1, 8, 7]), ZZ.map([1, 7, 1, 7]), 11, ZZ) >>> s, t, g ([5, 6], [6], [1, 7]) As result we obtained polynomials ``s = 5*x + 6`` and ``t = 6``, and additionally ``gcd(f, g) = x + 7``. This is correct because:: >>> S = gf_mul(s, ZZ.map([1, 8, 7]), 11, ZZ) >>> T = gf_mul(t, ZZ.map([1, 7, 1, 7]), 11, ZZ) >>> gf_add(S, T, 11, ZZ) == [1, 7] True References ========== .. [1] [Gathen99]_ """ if not (f or g): return [K.one], [], [] p0, r0 = gf_monic(f, p, K) p1, r1 = gf_monic(g, p, K) if not f: return [], [K.invert(p1, p)], r1 if not g: return [K.invert(p0, p)], [], r0 s0, s1 = [K.invert(p0, p)], [] t0, t1 = [], [K.invert(p1, p)] while True: Q, R = gf_div(r0, r1, p, K) if not R: break (lc, r1), r0 = gf_monic(R, p, K), r1 inv = K.invert(lc, p) s = gf_sub_mul(s0, s1, Q, p, K) t = gf_sub_mul(t0, t1, Q, p, K) s1, s0 = gf_mul_ground(s, inv, p, K), s1 t1, t0 = gf_mul_ground(t, inv, p, K), t1 return s1, t1, r1 def gf_monic(f, p, K): """ Compute LC and a monic polynomial in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_monic >>> gf_monic(ZZ.map([3, 2, 4]), 5, ZZ) (3, [1, 4, 3]) """ if not f: return K.zero, [] else: lc = f[0] if K.is_one(lc): return lc, list(f) else: return lc, gf_quo_ground(f, lc, p, K) def gf_diff(f, p, K): """ Differentiate polynomial in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_diff >>> gf_diff([3, 2, 4], 5, ZZ) [1, 2] """ df = gf_degree(f) h, n = [K.zero]*df, df for coeff in f[:-1]: coeff *= K(n) coeff %= p if coeff: h[df - n] = coeff n -= 1 return gf_strip(h) def gf_eval(f, a, p, K): """ Evaluate ``f(a)`` in ``GF(p)`` using Horner scheme. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_eval >>> gf_eval([3, 2, 4], 2, 5, ZZ) 0 """ result = K.zero for c in f: result *= a result += c result %= p return result def gf_multi_eval(f, A, p, K): """ Evaluate ``f(a)`` for ``a`` in ``[a_1, ..., a_n]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_multi_eval >>> gf_multi_eval([3, 2, 4], [0, 1, 2, 3, 4], 5, ZZ) [4, 4, 0, 2, 0] """ return [ gf_eval(f, a, p, K) for a in A ] def gf_compose(f, g, p, K): """ Compute polynomial composition ``f(g)`` in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_compose >>> gf_compose([3, 2, 4], [2, 2, 2], 5, ZZ) [2, 4, 0, 3, 0] """ if len(g) <= 1: return gf_strip([gf_eval(f, gf_LC(g, K), p, K)]) if not f: return [] h = [f[0]] for c in f[1:]: h = gf_mul(h, g, p, K) h = gf_add_ground(h, c, p, K) return h def gf_compose_mod(g, h, f, p, K): """ Compute polynomial composition ``g(h)`` in ``GF(p)[x]/(f)``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_compose_mod >>> gf_compose_mod(ZZ.map([3, 2, 4]), ZZ.map([2, 2, 2]), ZZ.map([4, 3]), 5, ZZ) [4] """ if not g: return [] comp = [g[0]] for a in g[1:]: comp = gf_mul(comp, h, p, K) comp = gf_add_ground(comp, a, p, K) comp = gf_rem(comp, f, p, K) return comp def gf_trace_map(a, b, c, n, f, p, K): """ Compute polynomial trace map in ``GF(p)[x]/(f)``. Given a polynomial ``f`` in ``GF(p)[x]``, polynomials ``a``, ``b``, ``c`` in the quotient ring ``GF(p)[x]/(f)`` such that ``b = c**t (mod f)`` for some positive power ``t`` of ``p``, and a positive integer ``n``, returns a mapping:: a -> a**t**n, a + a**t + a**t**2 + ... + a**t**n (mod f) In factorization context, ``b = x**p mod f`` and ``c = x mod f``. This way we can efficiently compute trace polynomials in equal degree factorization routine, much faster than with other methods, like iterated Frobenius algorithm, for large degrees. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_trace_map >>> gf_trace_map([1, 2], [4, 4], [1, 1], 4, [3, 2, 4], 5, ZZ) ([1, 3], [1, 3]) References ========== .. [1] [Gathen92]_ """ u = gf_compose_mod(a, b, f, p, K) v = b if n & 1: U = gf_add(a, u, p, K) V = b else: U = a V = c n >>= 1 while n: u = gf_add(u, gf_compose_mod(u, v, f, p, K), p, K) v = gf_compose_mod(v, v, f, p, K) if n & 1: U = gf_add(U, gf_compose_mod(u, V, f, p, K), p, K) V = gf_compose_mod(v, V, f, p, K) n >>= 1 return gf_compose_mod(a, V, f, p, K), U def _gf_trace_map(f, n, g, b, p, K): """ utility for ``gf_edf_shoup`` """ f = gf_rem(f, g, p, K) h = f r = f for i in range(1, n): h = gf_frobenius_map(h, g, b, p, K) r = gf_add(r, h, p, K) r = gf_rem(r, g, p, K) return r def gf_random(n, p, K): """ Generate a random polynomial in ``GF(p)[x]`` of degree ``n``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_random >>> gf_random(10, 5, ZZ) #doctest: +SKIP [1, 2, 3, 2, 1, 1, 1, 2, 0, 4, 2] """ pi = int(p) return [K.one] + [ K(int(uniform(0, pi))) for i in range(0, n) ] def gf_irreducible(n, p, K): """ Generate random irreducible polynomial of degree ``n`` in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_irreducible >>> gf_irreducible(10, 5, ZZ) #doctest: +SKIP [1, 4, 2, 2, 3, 2, 4, 1, 4, 0, 4] """ while True: f = gf_random(n, p, K) if gf_irreducible_p(f, p, K): return f def gf_irred_p_ben_or(f, p, K): """ Ben-Or's polynomial irreducibility test over finite fields. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_irred_p_ben_or >>> gf_irred_p_ben_or(ZZ.map([1, 4, 2, 2, 3, 2, 4, 1, 4, 0, 4]), 5, ZZ) True >>> gf_irred_p_ben_or(ZZ.map([3, 2, 4]), 5, ZZ) False """ n = gf_degree(f) if n <= 1: return True _, f = gf_monic(f, p, K) if n < 5: H = h = gf_pow_mod([K.one, K.zero], p, f, p, K) for i in range(0, n//2): g = gf_sub(h, [K.one, K.zero], p, K) if gf_gcd(f, g, p, K) == [K.one]: h = gf_compose_mod(h, H, f, p, K) else: return False else: b = gf_frobenius_monomial_base(f, p, K) H = h = gf_frobenius_map([K.one, K.zero], f, b, p, K) for i in range(0, n//2): g = gf_sub(h, [K.one, K.zero], p, K) if gf_gcd(f, g, p, K) == [K.one]: h = gf_frobenius_map(h, f, b, p, K) else: return False return True def gf_irred_p_rabin(f, p, K): """ Rabin's polynomial irreducibility test over finite fields. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_irred_p_rabin >>> gf_irred_p_rabin(ZZ.map([1, 4, 2, 2, 3, 2, 4, 1, 4, 0, 4]), 5, ZZ) True >>> gf_irred_p_rabin(ZZ.map([3, 2, 4]), 5, ZZ) False """ n = gf_degree(f) if n <= 1: return True _, f = gf_monic(f, p, K) x = [K.one, K.zero] from sympy.ntheory import factorint indices = { n//d for d in factorint(n) } b = gf_frobenius_monomial_base(f, p, K) h = b[1] for i in range(1, n): if i in indices: g = gf_sub(h, x, p, K) if gf_gcd(f, g, p, K) != [K.one]: return False h = gf_frobenius_map(h, f, b, p, K) return h == x _irred_methods = { 'ben-or': gf_irred_p_ben_or, 'rabin': gf_irred_p_rabin, } def gf_irreducible_p(f, p, K): """ Test irreducibility of a polynomial ``f`` in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_irreducible_p >>> gf_irreducible_p(ZZ.map([1, 4, 2, 2, 3, 2, 4, 1, 4, 0, 4]), 5, ZZ) True >>> gf_irreducible_p(ZZ.map([3, 2, 4]), 5, ZZ) False """ method = query('GF_IRRED_METHOD') if method is not None: irred = _irred_methods[method](f, p, K) else: irred = gf_irred_p_rabin(f, p, K) return irred def gf_sqf_p(f, p, K): """ Return ``True`` if ``f`` is square-free in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_sqf_p >>> gf_sqf_p(ZZ.map([3, 2, 4]), 5, ZZ) True >>> gf_sqf_p(ZZ.map([2, 4, 4, 2, 2, 1, 4]), 5, ZZ) False """ _, f = gf_monic(f, p, K) if not f: return True else: return gf_gcd(f, gf_diff(f, p, K), p, K) == [K.one] def gf_sqf_part(f, p, K): """ Return square-free part of a ``GF(p)[x]`` polynomial. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_sqf_part >>> gf_sqf_part(ZZ.map([1, 1, 3, 0, 1, 0, 2, 2, 1]), 5, ZZ) [1, 4, 3] """ _, sqf = gf_sqf_list(f, p, K) g = [K.one] for f, _ in sqf: g = gf_mul(g, f, p, K) return g def gf_sqf_list(f, p, K, all=False): """ Return the square-free decomposition of a ``GF(p)[x]`` polynomial. Given a polynomial ``f`` in ``GF(p)[x]``, returns the leading coefficient of ``f`` and a square-free decomposition ``f_1**e_1 f_2**e_2 ... f_k**e_k`` such that all ``f_i`` are monic polynomials and ``(f_i, f_j)`` for ``i != j`` are co-prime and ``e_1 ... e_k`` are given in increasing order. All trivial terms (i.e. ``f_i = 1``) are not included in the output. Consider polynomial ``f = x**11 + 1`` over ``GF(11)[x]``:: >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import ( ... gf_from_dict, gf_diff, gf_sqf_list, gf_pow, ... ) ... # doctest: +NORMALIZE_WHITESPACE >>> f = gf_from_dict({11: ZZ(1), 0: ZZ(1)}, 11, ZZ) Note that ``f'(x) = 0``:: >>> gf_diff(f, 11, ZZ) [] This phenomenon does not happen in characteristic zero. However we can still compute square-free decomposition of ``f`` using ``gf_sqf()``:: >>> gf_sqf_list(f, 11, ZZ) (1, [([1, 1], 11)]) We obtained factorization ``f = (x + 1)**11``. This is correct because:: >>> gf_pow([1, 1], 11, 11, ZZ) == f True References ========== .. [1] [Geddes92]_ """ n, sqf, factors, r = 1, False, [], int(p) lc, f = gf_monic(f, p, K) if gf_degree(f) < 1: return lc, [] while True: F = gf_diff(f, p, K) if F != []: g = gf_gcd(f, F, p, K) h = gf_quo(f, g, p, K) i = 1 while h != [K.one]: G = gf_gcd(g, h, p, K) H = gf_quo(h, G, p, K) if gf_degree(H) > 0: factors.append((H, i*n)) g, h, i = gf_quo(g, G, p, K), G, i + 1 if g == [K.one]: sqf = True else: f = g if not sqf: d = gf_degree(f) // r for i in range(0, d + 1): f[i] = f[i*r] f, n = f[:d + 1], n*r else: break if all: raise ValueError("'all=True' is not supported yet") return lc, factors def gf_Qmatrix(f, p, K): """ Calculate Berlekamp's ``Q`` matrix. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_Qmatrix >>> gf_Qmatrix([3, 2, 4], 5, ZZ) [[1, 0], [3, 4]] >>> gf_Qmatrix([1, 0, 0, 0, 1], 5, ZZ) [[1, 0, 0, 0], [0, 4, 0, 0], [0, 0, 1, 0], [0, 0, 0, 4]] """ n, r = gf_degree(f), int(p) q = [K.one] + [K.zero]*(n - 1) Q = [list(q)] + [[]]*(n - 1) for i in range(1, (n - 1)*r + 1): qq, c = [(-q[-1]*f[-1]) % p], q[-1] for j in range(1, n): qq.append((q[j - 1] - c*f[-j - 1]) % p) if not (i % r): Q[i//r] = list(qq) q = qq return Q def gf_Qbasis(Q, p, K): """ Compute a basis of the kernel of ``Q``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_Qmatrix, gf_Qbasis >>> gf_Qbasis(gf_Qmatrix([1, 0, 0, 0, 1], 5, ZZ), 5, ZZ) [[1, 0, 0, 0], [0, 0, 1, 0]] >>> gf_Qbasis(gf_Qmatrix([3, 2, 4], 5, ZZ), 5, ZZ) [[1, 0]] """ Q, n = [ list(q) for q in Q ], len(Q) for k in range(0, n): Q[k][k] = (Q[k][k] - K.one) % p for k in range(0, n): for i in range(k, n): if Q[k][i]: break else: continue inv = K.invert(Q[k][i], p) for j in range(0, n): Q[j][i] = (Q[j][i]*inv) % p for j in range(0, n): t = Q[j][k] Q[j][k] = Q[j][i] Q[j][i] = t for i in range(0, n): if i != k: q = Q[k][i] for j in range(0, n): Q[j][i] = (Q[j][i] - Q[j][k]*q) % p for i in range(0, n): for j in range(0, n): if i == j: Q[i][j] = (K.one - Q[i][j]) % p else: Q[i][j] = (-Q[i][j]) % p basis = [] for q in Q: if any(q): basis.append(q) return basis def gf_berlekamp(f, p, K): """ Factor a square-free ``f`` in ``GF(p)[x]`` for small ``p``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_berlekamp >>> gf_berlekamp([1, 0, 0, 0, 1], 5, ZZ) [[1, 0, 2], [1, 0, 3]] """ Q = gf_Qmatrix(f, p, K) V = gf_Qbasis(Q, p, K) for i, v in enumerate(V): V[i] = gf_strip(list(reversed(v))) factors = [f] for k in range(1, len(V)): for f in list(factors): s = K.zero while s < p: g = gf_sub_ground(V[k], s, p, K) h = gf_gcd(f, g, p, K) if h != [K.one] and h != f: factors.remove(f) f = gf_quo(f, h, p, K) factors.extend([f, h]) if len(factors) == len(V): return _sort_factors(factors, multiple=False) s += K.one return _sort_factors(factors, multiple=False) def gf_ddf_zassenhaus(f, p, K): """ Cantor-Zassenhaus: Deterministic Distinct Degree Factorization Given a monic square-free polynomial ``f`` in ``GF(p)[x]``, computes partial distinct degree factorization ``f_1 ... f_d`` of ``f`` where ``deg(f_i) != deg(f_j)`` for ``i != j``. The result is returned as a list of pairs ``(f_i, e_i)`` where ``deg(f_i) > 0`` and ``e_i > 0`` is an argument to the equal degree factorization routine. Consider the polynomial ``x**15 - 1`` in ``GF(11)[x]``:: >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_from_dict >>> f = gf_from_dict({15: ZZ(1), 0: ZZ(-1)}, 11, ZZ) Distinct degree factorization gives:: >>> from sympy.polys.galoistools import gf_ddf_zassenhaus >>> gf_ddf_zassenhaus(f, 11, ZZ) [([1, 0, 0, 0, 0, 10], 1), ([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 2)] which means ``x**15 - 1 = (x**5 - 1) (x**10 + x**5 + 1)``. To obtain factorization into irreducibles, use equal degree factorization procedure (EDF) with each of the factors. References ========== .. [1] [Gathen99]_ .. [2] [Geddes92]_ """ i, g, factors = 1, [K.one, K.zero], [] b = gf_frobenius_monomial_base(f, p, K) while 2*i <= gf_degree(f): g = gf_frobenius_map(g, f, b, p, K) h = gf_gcd(f, gf_sub(g, [K.one, K.zero], p, K), p, K) if h != [K.one]: factors.append((h, i)) f = gf_quo(f, h, p, K) g = gf_rem(g, f, p, K) b = gf_frobenius_monomial_base(f, p, K) i += 1 if f != [K.one]: return factors + [(f, gf_degree(f))] else: return factors def gf_edf_zassenhaus(f, n, p, K): """ Cantor-Zassenhaus: Probabilistic Equal Degree Factorization Given a monic square-free polynomial ``f`` in ``GF(p)[x]`` and an integer ``n``, such that ``n`` divides ``deg(f)``, returns all irreducible factors ``f_1,...,f_d`` of ``f``, each of degree ``n``. EDF procedure gives complete factorization over Galois fields. Consider the square-free polynomial ``f = x**3 + x**2 + x + 1`` in ``GF(5)[x]``. Let's compute its irreducible factors of degree one:: >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_edf_zassenhaus >>> gf_edf_zassenhaus([1,1,1,1], 1, 5, ZZ) [[1, 1], [1, 2], [1, 3]] Notes ===== The case p == 2 is handled by Cohen's Algorithm 3.4.8. The case p odd is as in Geddes Algorithm 8.9 (or Cohen's Algorithm 3.4.6). References ========== .. [1] [Gathen99]_ .. [2] [Geddes92]_ Algorithm 8.9 .. [3] [Cohen93]_ Algorithm 3.4.8 """ factors = [f] if gf_degree(f) <= n: return factors N = gf_degree(f) // n if p != 2: b = gf_frobenius_monomial_base(f, p, K) t = [K.one, K.zero] while len(factors) < N: if p == 2: h = r = t for i in range(n - 1): r = gf_pow_mod(r, 2, f, p, K) h = gf_add(h, r, p, K) g = gf_gcd(f, h, p, K) t += [K.zero, K.zero] else: r = gf_random(2 * n - 1, p, K) h = _gf_pow_pnm1d2(r, n, f, b, p, K) g = gf_gcd(f, gf_sub_ground(h, K.one, p, K), p, K) if g != [K.one] and g != f: factors = gf_edf_zassenhaus(g, n, p, K) \ + gf_edf_zassenhaus(gf_quo(f, g, p, K), n, p, K) return _sort_factors(factors, multiple=False) def gf_ddf_shoup(f, p, K): """ Kaltofen-Shoup: Deterministic Distinct Degree Factorization Given a monic square-free polynomial ``f`` in ``GF(p)[x]``, computes partial distinct degree factorization ``f_1,...,f_d`` of ``f`` where ``deg(f_i) != deg(f_j)`` for ``i != j``. The result is returned as a list of pairs ``(f_i, e_i)`` where ``deg(f_i) > 0`` and ``e_i > 0`` is an argument to the equal degree factorization routine. This algorithm is an improved version of Zassenhaus algorithm for large ``deg(f)`` and modulus ``p`` (especially for ``deg(f) ~ lg(p)``). Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_ddf_shoup, gf_from_dict >>> f = gf_from_dict({6: ZZ(1), 5: ZZ(-1), 4: ZZ(1), 3: ZZ(1), 1: ZZ(-1)}, 3, ZZ) >>> gf_ddf_shoup(f, 3, ZZ) [([1, 1, 0], 1), ([1, 1, 0, 1, 2], 2)] References ========== .. [1] [Kaltofen98]_ .. [2] [Shoup95]_ .. [3] [Gathen92]_ """ n = gf_degree(f) k = int(_ceil(_sqrt(n//2))) b = gf_frobenius_monomial_base(f, p, K) h = gf_frobenius_map([K.one, K.zero], f, b, p, K) # U[i] = x**(p**i) U = [[K.one, K.zero], h] + [K.zero]*(k - 1) for i in range(2, k + 1): U[i] = gf_frobenius_map(U[i-1], f, b, p, K) h, U = U[k], U[:k] # V[i] = x**(p**(k*(i+1))) V = [h] + [K.zero]*(k - 1) for i in range(1, k): V[i] = gf_compose_mod(V[i - 1], h, f, p, K) factors = [] for i, v in enumerate(V): h, j = [K.one], k - 1 for u in U: g = gf_sub(v, u, p, K) h = gf_mul(h, g, p, K) h = gf_rem(h, f, p, K) g = gf_gcd(f, h, p, K) f = gf_quo(f, g, p, K) for u in reversed(U): h = gf_sub(v, u, p, K) F = gf_gcd(g, h, p, K) if F != [K.one]: factors.append((F, k*(i + 1) - j)) g, j = gf_quo(g, F, p, K), j - 1 if f != [K.one]: factors.append((f, gf_degree(f))) return factors def gf_edf_shoup(f, n, p, K): """ Gathen-Shoup: Probabilistic Equal Degree Factorization Given a monic square-free polynomial ``f`` in ``GF(p)[x]`` and integer ``n`` such that ``n`` divides ``deg(f)``, returns all irreducible factors ``f_1,...,f_d`` of ``f``, each of degree ``n``. This is a complete factorization over Galois fields. This algorithm is an improved version of Zassenhaus algorithm for large ``deg(f)`` and modulus ``p`` (especially for ``deg(f) ~ lg(p)``). Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_edf_shoup >>> gf_edf_shoup(ZZ.map([1, 2837, 2277]), 1, 2917, ZZ) [[1, 852], [1, 1985]] References ========== .. [1] [Shoup91]_ .. [2] [Gathen92]_ """ N, q = gf_degree(f), int(p) if not N: return [] if N <= n: return [f] factors, x = [f], [K.one, K.zero] r = gf_random(N - 1, p, K) if p == 2: h = gf_pow_mod(x, q, f, p, K) H = gf_trace_map(r, h, x, n - 1, f, p, K)[1] h1 = gf_gcd(f, H, p, K) h2 = gf_quo(f, h1, p, K) factors = gf_edf_shoup(h1, n, p, K) \ + gf_edf_shoup(h2, n, p, K) else: b = gf_frobenius_monomial_base(f, p, K) H = _gf_trace_map(r, n, f, b, p, K) h = gf_pow_mod(H, (q - 1)//2, f, p, K) h1 = gf_gcd(f, h, p, K) h2 = gf_gcd(f, gf_sub_ground(h, K.one, p, K), p, K) h3 = gf_quo(f, gf_mul(h1, h2, p, K), p, K) factors = gf_edf_shoup(h1, n, p, K) \ + gf_edf_shoup(h2, n, p, K) \ + gf_edf_shoup(h3, n, p, K) return _sort_factors(factors, multiple=False) def gf_zassenhaus(f, p, K): """ Factor a square-free ``f`` in ``GF(p)[x]`` for medium ``p``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_zassenhaus >>> gf_zassenhaus(ZZ.map([1, 4, 3]), 5, ZZ) [[1, 1], [1, 3]] """ factors = [] for factor, n in gf_ddf_zassenhaus(f, p, K): factors += gf_edf_zassenhaus(factor, n, p, K) return _sort_factors(factors, multiple=False) def gf_shoup(f, p, K): """ Factor a square-free ``f`` in ``GF(p)[x]`` for large ``p``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_shoup >>> gf_shoup(ZZ.map([1, 4, 3]), 5, ZZ) [[1, 1], [1, 3]] """ factors = [] for factor, n in gf_ddf_shoup(f, p, K): factors += gf_edf_shoup(factor, n, p, K) return _sort_factors(factors, multiple=False) _factor_methods = { 'berlekamp': gf_berlekamp, # ``p`` : small 'zassenhaus': gf_zassenhaus, # ``p`` : medium 'shoup': gf_shoup, # ``p`` : large } def gf_factor_sqf(f, p, K, method=None): """ Factor a square-free polynomial ``f`` in ``GF(p)[x]``. Examples ======== >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_factor_sqf >>> gf_factor_sqf(ZZ.map([3, 2, 4]), 5, ZZ) (3, [[1, 1], [1, 3]]) """ lc, f = gf_monic(f, p, K) if gf_degree(f) < 1: return lc, [] method = method or query('GF_FACTOR_METHOD') if method is not None: factors = _factor_methods[method](f, p, K) else: factors = gf_zassenhaus(f, p, K) return lc, factors def gf_factor(f, p, K): """ Factor (non square-free) polynomials in ``GF(p)[x]``. Given a possibly non square-free polynomial ``f`` in ``GF(p)[x]``, returns its complete factorization into irreducibles:: f_1(x)**e_1 f_2(x)**e_2 ... f_d(x)**e_d where each ``f_i`` is a monic polynomial and ``gcd(f_i, f_j) == 1``, for ``i != j``. The result is given as a tuple consisting of the leading coefficient of ``f`` and a list of factors of ``f`` with their multiplicities. The algorithm proceeds by first computing square-free decomposition of ``f`` and then iteratively factoring each of square-free factors. Consider a non square-free polynomial ``f = (7*x + 1) (x + 2)**2`` in ``GF(11)[x]``. We obtain its factorization into irreducibles as follows:: >>> from sympy.polys.domains import ZZ >>> from sympy.polys.galoistools import gf_factor >>> gf_factor(ZZ.map([5, 2, 7, 2]), 11, ZZ) (5, [([1, 2], 1), ([1, 8], 2)]) We arrived with factorization ``f = 5 (x + 2) (x + 8)**2``. We did not recover the exact form of the input polynomial because we requested to get monic factors of ``f`` and its leading coefficient separately. Square-free factors of ``f`` can be factored into irreducibles over ``GF(p)`` using three very different methods: Berlekamp efficient for very small values of ``p`` (usually ``p < 25``) Cantor-Zassenhaus efficient on average input and with "typical" ``p`` Shoup-Kaltofen-Gathen efficient with very large inputs and modulus If you want to use a specific factorization method, instead of the default one, set ``GF_FACTOR_METHOD`` with one of ``berlekamp``, ``zassenhaus`` or ``shoup`` values. References ========== .. [1] [Gathen99]_ """ lc, f = gf_monic(f, p, K) if gf_degree(f) < 1: return lc, [] factors = [] for g, n in gf_sqf_list(f, p, K)[1]: for h in gf_factor_sqf(g, p, K)[1]: factors.append((h, n)) return lc, _sort_factors(factors) def gf_value(f, a): """ Value of polynomial 'f' at 'a' in field R. Examples ======== >>> from sympy.polys.galoistools import gf_value >>> gf_value([1, 7, 2, 4], 11) 2204 """ result = 0 for c in f: result *= a result += c return result def linear_congruence(a, b, m): """ Returns the values of x satisfying a*x congruent b mod(m) Here m is positive integer and a, b are natural numbers. This function returns only those values of x which are distinct mod(m). Examples ======== >>> from sympy.polys.galoistools import linear_congruence >>> linear_congruence(3, 12, 15) [4, 9, 14] There are 3 solutions distinct mod(15) since gcd(a, m) = gcd(3, 15) = 3. References ========== .. [1] https://en.wikipedia.org/wiki/Linear_congruence_theorem """ from sympy.polys.polytools import gcdex if a % m == 0: if b % m == 0: return list(range(m)) else: return [] r, _, g = gcdex(a, m) if b % g != 0: return [] return [(r * b // g + t * m // g) % m for t in range(g)] def _raise_mod_power(x, s, p, f): """ Used in gf_csolve to generate solutions of f(x) cong 0 mod(p**(s + 1)) from the solutions of f(x) cong 0 mod(p**s). Examples ======== >>> from sympy.polys.galoistools import _raise_mod_power >>> from sympy.polys.galoistools import csolve_prime These is the solutions of f(x) = x**2 + x + 7 cong 0 mod(3) >>> f = [1, 1, 7] >>> csolve_prime(f, 3) [1] >>> [ i for i in range(3) if not (i**2 + i + 7) % 3] [1] The solutions of f(x) cong 0 mod(9) are constructed from the values returned from _raise_mod_power: >>> x, s, p = 1, 1, 3 >>> V = _raise_mod_power(x, s, p, f) >>> [x + v * p**s for v in V] [1, 4, 7] And these are confirmed with the following: >>> [ i for i in range(3**2) if not (i**2 + i + 7) % 3**2] [1, 4, 7] """ from sympy.polys.domains import ZZ f_f = gf_diff(f, p, ZZ) alpha = gf_value(f_f, x) beta = - gf_value(f, x) // p**s return linear_congruence(alpha, beta, p) def _csolve_prime_las_vegas(f, p, seed=None): r""" Solutions of `f(x) \equiv 0 \pmod{p}`, `f(0) \not\equiv 0 \pmod{p}`. Explanation =========== This algorithm is classified as the Las Vegas method. That is, it always returns the correct answer and solves the problem fast in many cases, but if it is unlucky, it does not answer forever. Suppose the polynomial f is not a zero polynomial. Assume further that it is of degree at most p-1 and `f(0)\not\equiv 0 \pmod{p}`. These assumptions are not an essential part of the algorithm, only that it is more convenient for the function calling this function to resolve them. Note that `x^{p-1} - 1 \equiv \prod_{a=1}^{p-1}(x - a) \pmod{p}`. Thus, the greatest common divisor with f is `\prod_{s \in S}(x - s)`, with S being the set of solutions to f. Furthermore, when a is randomly determined, `(x+a)^{(p-1)/2}-1` is a polynomial with (p-1)/2 randomly chosen solutions. The greatest common divisor of f may be a nontrivial factor of f. When p is large and the degree of f is small, it is faster than naive solution methods. Parameters ========== f : polynomial p : prime number Returns ======= list[int] a list of solutions, sorted in ascending order by integers in the range [1, p). The same value does not exist in the list even if there is a multiple solution. If no solution exists, returns []. Examples ======== >>> from sympy.polys.galoistools import _csolve_prime_las_vegas >>> _csolve_prime_las_vegas([1, 4, 3], 7) # x^2 + 4x + 3 = 0 (mod 7) [4, 6] >>> _csolve_prime_las_vegas([5, 7, 1, 9], 11) # 5x^3 + 7x^2 + x + 9 = 0 (mod 11) [1, 5, 8] References ========== .. [1] R. Crandall and C. Pomerance "Prime Numbers", 2nd Ed., Algorithm 2.3.10 """ from sympy.polys.domains import ZZ from sympy.ntheory import sqrt_mod randint = _randint(seed) root = set() g = gf_pow_mod([1, 0], p - 1, f, p, ZZ) g = gf_sub_ground(g, 1, p, ZZ) # We want to calculate gcd(x**(p-1) - 1, f(x)) factors = [gf_gcd(f, g, p, ZZ)] while factors: f = factors.pop() # If the degree is small, solve directly if len(f) <= 1: continue if len(f) == 2: root.add(-invert(f[0], p) * f[1] % p) continue if len(f) == 3: inv = invert(f[0], p) b = f[1] * inv % p b = (b + p * (b % 2)) // 2 root.update((r - b) % p for r in sqrt_mod(b**2 - f[2] * inv, p, all_roots=True)) continue while True: # Determine `a` randomly and # compute gcd((x+a)**((p-1)//2)-1, f(x)) a = randint(0, p - 1) g = gf_pow_mod([1, a], (p - 1) // 2, f, p, ZZ) g = gf_sub_ground(g, 1, p, ZZ) g = gf_gcd(f, g, p, ZZ) if 1 < len(g) < len(f): factors.append(g) factors.append(gf_div(f, g, p, ZZ)[0]) break return sorted(root) def csolve_prime(f, p, e=1): r""" Solutions of `f(x) \equiv 0 \pmod{p^e}`. Parameters ========== f : polynomial p : prime number e : positive integer Returns ======= list[int] a list of solutions, sorted in ascending order by integers in the range [1, p**e). The same value does not exist in the list even if there is a multiple solution. If no solution exists, returns []. Examples ======== >>> from sympy.polys.galoistools import csolve_prime >>> csolve_prime([1, 1, 7], 3, 1) [1] >>> csolve_prime([1, 1, 7], 3, 2) [1, 4, 7] Solutions [7, 4, 1] (mod 3**2) are generated by ``_raise_mod_power()`` from solution [1] (mod 3). """ from sympy.polys.domains import ZZ g = [MPZ(int(c)) for c in f] # Convert to polynomial of degree at most p-1 for i in range(len(g) - p): g[i + p - 1] += g[i] g[i] = 0 g = gf_trunc(g, p) # Checks whether g(x) is divisible by x k = 0 while k < len(g) and g[len(g) - k - 1] == 0: k += 1 if k: g = g[:-k] root_zero = [0] else: root_zero = [] if g == []: X1 = list(range(p)) elif len(g)**2 < p: # The conditions under which `_csolve_prime_las_vegas` is faster than # a naive solution are worth considering. X1 = root_zero + _csolve_prime_las_vegas(g, p) else: X1 = root_zero + [i for i in range(p) if gf_eval(g, i, p, ZZ) == 0] if e == 1: return X1 X = [] S = list(zip(X1, [1]*len(X1))) while S: x, s = S.pop() if s == e: X.append(x) else: s1 = s + 1 ps = p**s S.extend([(x + v*ps, s1) for v in _raise_mod_power(x, s, p, f)]) return sorted(X) def gf_csolve(f, n): """ To solve f(x) congruent 0 mod(n). n is divided into canonical factors and f(x) cong 0 mod(p**e) will be solved for each factor. Applying the Chinese Remainder Theorem to the results returns the final answers. Examples ======== Solve [1, 1, 7] congruent 0 mod(189): >>> from sympy.polys.galoistools import gf_csolve >>> gf_csolve([1, 1, 7], 189) [13, 49, 76, 112, 139, 175] See Also ======== sympy.ntheory.residue_ntheory.polynomial_congruence : a higher level solving routine References ========== .. [1] 'An introduction to the Theory of Numbers' 5th Edition by Ivan Niven, Zuckerman and Montgomery. """ from sympy.polys.domains import ZZ from sympy.ntheory import factorint P = factorint(n) X = [csolve_prime(f, p, e) for p, e in P.items()] pools = list(map(tuple, X)) perms = [[]] for pool in pools: perms = [x + [y] for x in perms for y in pool] dist_factors = [pow(p, e) for p, e in P.items()] return sorted([gf_crt(per, dist_factors, ZZ) for per in perms])