Example #1
0
def sturm(f):
    """Compute the Sturm sequence of given polynomial.

    Usage:
    ======
        The input is assumed to be a square-free and univariate
        polynomial, either as a SymPy expression or as instance of
        Polynomial.

        The output is a list representing f's Sturm sequence, which is
        built similarly to the euclidian algorithm, beginning with f
        and its derivative.

        The result can be used in L{count_real_roots}.

    References:
    ===========
        Davenport, Siret, Tournier: Computer Algebra, 1988

    """

    if not isinstance(f, Polynomial):
        f = Polynomial(f)
    seq = [f]
    seq.append(f.diff(f.var[0]))
    while seq[-1].sympy_expr is not S.Zero:
        seq.append(-(div_.div(seq[-2], seq[-1])[-1]))
    return seq[:-1]
Example #2
0
def sqf_part(f, var=None, order=None):
    """Returns the square-free part of f.

    Usage:
    ======
        Computes the square-free part of f.

        The input is assumed to be a univariate polynomial, either as
        a SymPy expression or an instance of Polynomial. In the first
        case, you can optionally specify the variables and monomial
        order with the arguments 'var' and 'order'.

        The result is returned as an instance of Polynomial.
    
    Examples:
    =========
        >>> x = Symbol('x')
        >>> print sqf_part(2*x**3 + 2*x**2)
        2*x + 2*x**2

    References:
    ===========
        Gathen, Gerhard: Modern Computer Algebra,
        Cambridge University Press, 1. edition, p. 370

    Also see L{sqf}.

    """

    if not isinstance(f, Polynomial):
        f = Polynomial(f, var=var, order=order)

    # The gcd of f with its derivative is the multiple part.
    ff = div_.gcd(f, f.diff(f.var[0]))
    return div_.div(f, ff)[0]
Example #3
0
def div(f, g, var=None, order=None, coeff=None):
    """Division with remainder.

    A thin wrapper that returns SymPy expressions coming from L{div_.div}.

    """

    q, r = div_.div(f, g, var, order, coeff)

    if isinstance(q, list):
        return map(lambda x: x.sympy_expr, q), r.sympy_expr
    else:
        return q.sympy_expr, r.sympy_expr
Example #4
0
def sqf(f, var=None, order=None, coeff=None):
    """Square-free decomposition.

    Usage:
    ======
        Computes a decomposition of f in a1 * a2**2 * ... * an**n,
        where the ai are pairwise prime and square-free polynomials.

        The input is assumed to be a univariate polynomial, either as
        a SymPy expression or an instance of Polynomial. In the first
        case, you can optionally specify the variables and monomial
        order with the arguments 'var' and 'order'.

        If the argument 'coeff' is set to 'int', the constant factors
        are redistributed to the different ai, so that they have
        integer coefficients. Otherwise, they are made monic.

        A list is returned, with an instance of Polynomial at each
        index, which represents the multiplicity (except beginning
        with 1 instead of 0).
    
    Examples:
    =========
        >>> x = Symbol('x')
        >>> a = sqf(3 - 12*x - 4*x**3 + 4*x**4 + 13*x**2)
        >>> for i, f in enumerate(a): print (i + 1), f
        1 12 + 4*x**2
        2 (-1/2) + x
        >>> b = sqf(3 - 12*x - 4*x**3 + 4*x**4 + 13*x**2, coeff='int')
        >>> for i, f in enumerate(b): print (i + 1), f
        1 3 + x**2
        2 (-1) + 2*x
        
    References:
    ===========
        Gathen, Gerhard: Modern Computer Algebra,
        Cambridge University Press, 1. edition, p. 371

    Also see L{sqf_part}, L{factor}.

    """

    if not isinstance(f, Polynomial):
        f = Polynomial(f, var=var, order=order)

    # Check for constant polynomials:
    if f.var == [] or f.coeffs[0][1] is S.Zero:
        return [f]

    f = [f]
    while f[-1].coeffs[0][1] is not S.Zero:
        f.append(div_.gcd(f[-1], f[-1].diff(f[-1].var[0])))
    g = []
    for i in range(1, len(f)):
        g.append(div_.div(f[i - 1], f[i])[0])
    a = []
    for i in range(0, len(g) - 1):
        a.append(div_.div(g[i], g[i + 1])[0])
    a.append(g[-1])

    if coeff == "int":  # Redistribute the constants.
        ca = int(a[0].content())
        c = ca
        for i, p in enumerate(a[1:]):
            # Compute lcm of denominators in coeffs:
            l, a[i + 1] = p.as_integer()
            c /= int(l) ** (i + 2)
        ca = Integer(ca / c)
        a[0] = Polynomial(coeffs=tuple([(t[0] / ca,) + t[1:] for t in a[0].coeffs]), var=a[0].var, order=a[0].order)
    return a
Example #5
0
def kronecker_mv(f):
    """One step in multivariate factorization, see L{factor}."""

    # Given a list of factors, re-assemble all m-subsets of them.
    def factor_combinations(lisp, m):
        def recursion(fa, lisp, m):
            if m == 0:
                yield fa
            else:
                for i, fa2 in enumerate(lisp[0 : len(lisp) + 1 - m]):
                    for el in recursion(fa2 * fa, lisp[i + 1 :], m - 1):
                        yield el

        for i, fa in enumerate(lisp[0 : len(lisp) + 1 - m]):
            for el in recursion(fa, lisp[i + 1 :], m - 1):
                yield el

    # First sort the variables by occuring exponents.
    # Then get degree bound, that is larger than all individual degrees.
    max_exp = {}
    for v in f.var:
        max_exp[v] = 0
    for term in f.coeffs:
        for v, exponent in zip(f.var, term[1:]):
            if exponent > max_exp[v]:
                max_exp[v] = exponent
    new_var = list(f.var)
    new_var.sort(key=lambda v: max_exp[v], reverse=True)
    f = Polynomial(f.sympy_expr, var=new_var, order=f.order)
    d = int(max_exp[f.var[0]]) + 1

    # Now reduce the polynomial f to g in just variable, by the
    # substitution x_i -> y**(d**i)
    g = f.sympy_expr
    y = Symbol("y", dummy=True)
    for i, v in enumerate(f.var):
        g = g.subs(v, y ** (d ** i))
    g = Polynomial(g, var=y, order=f.order)

    # We can now call the univariate factorization algorithm for g.
    g_factors = factor(g)[1:]  # Don't use constant factor.
    constant_factor = Polynomial(S.One, var=f.var, order=f.order)

    # Trial division with all combinations of factors of g.
    tested = []
    result = []
    for m in range(1, len(g_factors) / 2 + 1):
        for cand in factor_combinations(g_factors, m):
            if cand in tested:
                continue
            # Inverse reduction
            ff = S.Zero
            for term in cand.coeffs:
                ff_term = term[0]
                y_deg = term[1]
                for v in f.var:
                    v_deg = int(y_deg) % d
                    y_deg = (y_deg - v_deg) / d
                    ff_term *= v ** v_deg
                ff += ff_term
            if ff is S.One:
                continue
            candidate = Polynomial(ff, var=f.var, order=f.order)
            # Make leading_coefficient positive:
            if candidate.coeffs[0][0] < 0:
                candidate = Polynomial(
                    coeffs=[(-t[0],) + t[1:] for t in candidate.coeffs], var=candidate.var, order=candidate.order
                )
            q, r = div_.div(f, candidate, coeff="int")
            if r.sympy_expr is S.Zero:  # found a factor
                result.append(candidate)
                f = q
            else:
                tested.append(cand)
            # Check if f is constant.
            if f.coeffs[0][1:] == tuple([S.Zero] * len(f.var)):
                constant_factor = f
                f = Polynomial(S.One, var=f.var, order=f.order)
                break
        if f.sympy_expr is S.One:
            break
    if f.sympy_expr is not S.One:
        result.append(f)

    return [constant_factor] + result
Example #6
0
def kronecker(f):
    """One step in univariate factorization, see L{factor}."""

    def lagrange_base(pos):
        """Compute the base polynomials used for Lagrange interpolation.

        They are constructed such that they evaluate to 1 at their
        position but 0 at all other points.

        """

        l = []
        for x in pos:
            l.append(Polynomial(coeffs=((S.One, S.One), (-x, S.Zero)), var=f.var, order=f.order))
        b = []
        for i, x in enumerate(pos):
            p = Polynomial(coeffs=((S.One, S.Zero),), var=f.var, order=f.order)
            for ll in l[:i] + l[i + 1 :]:
                p *= ll
            c = S.One
            for xx in pos[:i] + pos[i + 1 :]:
                c *= x - xx
            p = Polynomial(coeffs=tuple(map(lambda t: (t[0] / c,) + t[1:], p.coeffs)), var=p.var, order=p.order)
            b.append(p)
        return b

    def combine(divs):
        """Combine the divisors of all coefficients."""
        # Don't try negative divisors for first value.
        lst = map(lambda el: [el], divs[0])
        for choices in divs[1:]:
            lst2 = []
            for el in lst:
                for new in choices:
                    # Also try negative divisors:
                    lst2.append(el + [new])
                    lst2.append(el + [-new])
            lst = lst2
        return lst

    # Half the degree for a possible polynomial divisor g.
    deg = int(f.coeffs[0][1]) / 2
    # Points for interpolation
    pos = map(Rational, range(0 - deg / 2, deg + 1 - deg / 2))
    # Reusable Lagrange base polynomials.
    base = lagrange_base(pos)
    # Evaluate at points.
    values = map(f, pos)
    # All divisors of the values give possible values for g.
    divs = map(integer_divisors, map(int, values))
    # Assemble all possible divisor combinations.
    combs = combine(divs)
    # Construct candidates for g.
    cands = []
    for comb in combs:
        cand = Polynomial(S.Zero, var=f.var, order=f.order)
        for c, b in zip(comb, base):
            cand += Polynomial(coeffs=tuple([(c * term[0],) + term[1:] for term in b.coeffs]), var=b.var, order=b.order)

        # Filter out constant and non-integer polynomials!
        if not (len(cand.coeffs) == 1 and cand.coeffs[0][1] is S.Zero):
            if all(map(lambda t: t[0].is_integer, cand.coeffs)):
                cands.append(cand)

    # Make leading coefficient positive:
    for cand in cands:
        if cand.coeffs[0][0] < S.Zero:
            cand = Polynomial(
                coeffs=tuple([(t[0] * S.NegativeOne,) + t[1:] for t in cand.coeffs]), var=cand.var, order=cand.order
            )

    # Filter double entries:
    cands2 = []
    for cand in cands:
        if not cand in cands2:
            cands2.append(cand)
    cands = cands2

    # TODO: Too many candidates?
    # TODO: Use iterators instead of lists!

    for g in cands:
        q, r = div_.div(f, g, coeff="int")
        if r.sympy_expr is S.Zero:
            return kronecker(q) + kronecker(g)
    else:
        # No divisor found, f irreducible.
        # TODO: Try again with divisors of smaller degree?
        return [f]
Example #7
0
def groebner(f, var=None, order=None, reduced=True):
    """Computes a (reduced) Groebner base for a given list of polynomials.

    Usage:
    ======
        The input consists of a list of polynomials, either as SymPy
        expressions or instances of Polynomials. In the first case,
        you should also specify the variables and the monomial order
        with the arguments 'var' and 'order'. Only the first
        polynomial is checked for its type, the rest is assumed to
        match.

        By default, this algorithm returns the unique reduced Groebner
        base for the given ideal. By setting reduced=False, you can
        prevent the reduction steps.

    Examples:
    =========
        >>> x, y = symbols('xy')
        >>> G = groebner([x**2 + y**3, y**2-x], order='lex')
        >>> for g in G: print g
        x - y**2
        y**3 + y**4

    Notes:
    ======
        Groebner bases are used to choose specific generators for a
        polynomial ideal. Because these bases are unique, you can
        check for ideal equality, by comparing the Groebner bases. To
        see if one polynomial lies in on ideal, divide by the elements
        in the base and see if the remainder if 0. They can also be
        applied to equation systems: By choosing lexicographic
        ordering, you can eliminate one variable at a time, given that
        the ideal is zero-dimensional (finite number of solutions).

        Here, an improved version of Buchberger's algorithm is
        used. For all pairs of polynomials, the s-polynomial is
        computed, by mutually eliminating the leading terms of these 2
        polynomials. It's remainder (after division by the base) is
        then added. Sometimes, it is easy to see, that one
        s-polynomial will be reduced to 0 before computing it. At the
        end, the base is reduced, by trying to eliminate as many terms
        as possible with the leading terms of other base elements. The
        final step is to make all polynomials monic.
        
    References:
    ===========
        Cox, Little, O'Shea: Ideals, Varieties and Algorithms,
        Springer, 2. edition, p. 87
        
    """

    if isinstance(f, Basic):
        f = [f]
    if not isinstance(f[0], Polynomial):
        if var is None:
            var = merge_var(*map(lambda p: p.atoms(type=Symbol), f))
        if isinstance(var, Symbol):
            var = [var]
        f = map(lambda p: Polynomial(p, var=var, order=order), f)

    # Filter out the zero elements.
    f = filter(lambda p: p.sympy_expr is not S.Zero, f)

    # Empty Ideal.
    if len(f) == 0:
        return [Polynomial(S.Zero)]

    # Stores the unchecked combinations for s-poly's.
    b = []
    s = len(f)
    for i in range(0, s - 1):
        for j in range(i + 1, s):
            b.append((i, j))

    while b:
        # TODO: Choose better (non-arbitrary) pair: sugar method?
        i, j = b[0]
        crit = False
        lcm = term_lcm(f[i].coeffs[0], f[j].coeffs[0])
        # Check if leading terms are relativly prime.
        if lcm[1:] != term_mult(f[i].coeffs[0], f[j].coeffs[0])[1:]:
            # TODO: Don't operate on the whole lists, do nested ifs instead?
            kk = filter(lambda k: k != i and k != j, range(0, s))
            kk = filter(lambda k: not (min(i, k), max(i, k)) in b, kk)
            kk = filter(lambda k: not (min(j, k), max(j, k)) in b, kk)
            # Check if the lcm is divisible by another base element.
            kk = filter(lambda k: term_is_mult(lcm, f[k].coeffs[0]), kk)
            crit = not bool(kk)
        if crit:
            factor_i = Polynomial(coeffs=(term_div(lcm, f[i].coeffs[0]),), var=f[0].var, order=f[0].order)
            factor_j = Polynomial(coeffs=(term_div(lcm, f[j].coeffs[0]),), var=f[0].var, order=f[0].order)
            s_poly = f[i] * factor_i - f[j] * factor_j
            s_poly = div_.div(s_poly, f)[-1]  # reduce
            if s_poly.sympy_expr is not S.Zero:
                # we still have to add it to the base.
                s += 1
                f.append(s_poly)
                for t in range(0, s - 1):  # With a new element come
                    b.append((t, s - 1))  # new combinationas to test.
        b = b[1:]  # Checked one more.

    # We now have one possible Groebner base, probably too big.
    if not reduced:
        return f

    # We can get rid of all elements, where the leading term can be
    # reduced in the ideal of the remaining leading terms, that is,
    # can be divided by one of the other leading terms.
    blacklist = []
    for p in f:
        if filter(lambda x: term_is_mult(p.coeffs[0], x.coeffs[0]), filter(lambda x: not x in blacklist and x != p, f)):
            blacklist.append(p)
    for p in blacklist:
        f.remove(p)

    # We can now sort the basis elements according to their leading
    # term.
    f.sort(cmp=lambda a, b: term_cmp(a.coeffs[0], b.coeffs[0], a.order), reverse=True)

    # Divide all basis elements by their leading coefficient, to get a
    # leading 1.
    for i, p in enumerate(f):
        c = p.coeffs[0][0]
        f[i] = Polynomial(coeffs=tuple(map(lambda t: (t[0] / c,) + t[1:], p.coeffs)), var=p.var, order=p.order)

    # We now have a minimal Groebner basis, which is still not unique.
    # The next step is to reduce all basis elements in respect to the
    # rest of the base (without touching the leading terms).
    # As the basis is already sorted, the rest gets smaller each time.
    for i, p in enumerate(f[0:-1]):
        pp = div_.div(p, f[i + 1 :])[-1]
        f[i] = pp

    return f