Exemple #1
0
def normal(expr, *syms):
    p, q = together(expr).as_numer_denom()

    if p.is_polynomial(*syms) and q.is_polynomial(*syms):
        from sympy.polynomials import gcd, quo

        G = gcd(p, q, syms)

        if not isinstance(G, Basic.One):
            p = quo(p, G, syms)
            q = quo(q, G, syms)

    return p / q
Exemple #2
0
    def _eval_product(self, term=None):
        k = self.index
        a = self.lower
        n = self.upper

        if term is None:
            term = self.term

        if not term.has(k):
            return term ** (n - a + 1)
        elif term.is_polynomial(k):
            poly = term.as_polynomial(k)

            A = B = Q = S.One
            C = poly.leading_coeff()

            all_roots = roots(poly)

            for r in all_roots:
                A *= Basic.RisingFactorial(a - r, n - a + 1)
                Q *= n - r

            if len(all_roots) < poly.degree():
                B = Product(quo(poly, Q, k), (k, a, n))

            return C ** (n - a + 1) * A * B
        elif isinstance(term, Basic.Add):
            p, q = term.as_numer_denom()

            p = self._eval_product(p)
            q = self._eval_product(q)

            return p / q
        elif isinstance(term, Basic.Mul):
            exclude, include = [], []

            for t in term:
                p = self._eval_product(t)

                if p is not None:
                    exclude.append(p)
                else:
                    include.append(p)

            if not exclude:
                return None
            else:
                A, B = Mul(*exclude), Mul(*include)
                return A * Product(B, (k, a, n))
        elif isinstance(term, Basic.Pow):
            if not term.base.has(k):
                s = sum(term.exp, (k, a, n))

                if not isinstance(s, Sum):
                    return term.base ** s
            elif not term.exp.has(k):
                p = self._eval_product(term.base)

                if p is not None:
                    return p ** term.exp
Exemple #3
0
def ext_gcd(p, q, x):
    U = (p, S.One, S.Zero)
    V = (q, S.Zero, S.One)

    while True:
        q = quo(U[0], V[0], x)

        U, V = V, [ (a - q*b).expand() for a, b in zip(U, V) ]

        if isinstance(V[0], Basic.Zero):
            return U
Exemple #4
0
def factorization(poly, linear=False):
    """Returns a list with polynomial factors over rationals or, if
       'linear' flag is set over Q(i). This simple handler should
       be merged with original factor() method.

       >>> from sympy import *
       >>> x, y = symbols('xy')

       >>> factorization(x**2 - y**2)
       set([1, x - y, x + y])

       >>> factorization(x**2 + 1)
       set([1, 1 + x**2])

       >>> factorization(x**2 + 1, linear=True)
       set([1, x - I, I + x])

    """
    factored = set([ q.as_basic() for q in factor_.factor(poly) ])

    if not linear:
        return factored
    else:
        factors = []

        for factor in factored:
            symbols = factor.atoms(Symbol)

            if len(symbols) == 1:
                x = symbols.pop()
            else:
                factors += [ factor ]
                continue

            linearities = [ x - r for r in roots(factor, x) ]

            if not linearities:
                factors += [ factor ]
            else:
                unfactorable = quo(factor, Basic.Mul(*linearities))

                if not isinstance(unfactorable, Basic.Number):
                    factors += [ unfactorable ]

                factors += linearities

        return set(factors)
Exemple #5
0
    def splitter(p):
        for y in p.atoms(Basic.Symbol):
            if not isinstance(derivation(y), Basic.Zero):
                c, q = p.as_polynomial(y).as_primitive()

                q = q.as_basic()

                h = gcd(q, derivation(q), y)
                s = quo(h, gcd(q, q.diff(y), y), y)

                c_split = splitter(c)

                if s.as_polynomial(y).degree() == 0:
                    return (c_split[0], q * c_split[1])

                q_split = splitter(normal(q / s, *V))

                return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1])
        else:
            return (S.One, p)
Exemple #6
0
def normal(f, g, n):
    """Given relatively prime univariate polynomials 'f' and 'g',
       rewrite their quotient to a normal form defined as follows:

                       f(n)       A(n) C(n+1)
                       ----  =  Z -----------
                       g(n)       B(n)  C(n)

       where Z is arbitrary constant and A, B, C are monic
       polynomials in 'n' with follwing properties:

           (1) gcd(A(n), B(n+h)) = 1 for all 'h' in N
           (2) gcd(B(n), C(n+1)) = 1
           (3) gcd(A(n), C(n)) = 1

       This normal form, or rational factorization in other words,
       is crucial step in Gosper's algorithm and in difference
       equations solving. It can be also used to decide if two
       hypergeometric are similar or not.

       This procedure will return return triple containig elements
       of this factorization in the form (Z*A, B, C). For example:

       >>> from sympy import Symbol
       >>> n = Symbol('n', integer=True)

       >>> normal(4*n+5, 2*(4*n+1)*(2*n+3), n)
       (1/4, 3/2 + n, 1/4 + n)

    """
    f, g = map(Basic.sympify, (f, g))

    if f.is_polynomial:
        p = f.as_polynomial(n)
    else:
        raise ValueError("'f' must be a polynomial")

    if g.is_polynomial:
        q = g.as_polynomial(n)
    else:
        raise ValueError("'g' must be a polynomial")

    a, p = p.as_monic()
    b, q = q.as_monic()

    A = p.sympy_expr
    B = q.sympy_expr

    C, Z = S.One, a / b

    h = Symbol('h', dummy=True)

    res = resultant(A, B.subs(n, n+h), n)

    if not res.is_polynomial(h):
        res = quo(*res.as_numer_denom())

    _nni_roots = nni_roots(res, h)

    if _nni_roots == []:
        return (f, g, S.One)
    else:
        _nni_roots.sort()

        for i in _nni_roots:
            d = gcd(A, B.subs(n, n+i), n)

            A = quo(A, d, n)
            B = quo(B, d.subs(n, n-i), n)

            C *= Mul(*[ d.subs(n, n-j) for j in xrange(1, i+1) ])

        return (Z*A, B, C)
Exemple #7
0
def hypersimp(term, n, consecutive=True, simplify=True):
    """Given combinatorial term a(n) simplify its consecutive term
       ratio ie. a(n+1)/a(n). The term can be composed of functions
       and integer sequences which have equivalent represenation
       in terms of gamma special function. Currently ths includes
       factorials (falling, rising), binomials and gamma it self.

       The algorithm performs three basic steps:

           (1) Rewrite all functions in terms of gamma, if possible.

           (2) Rewrite all occurences of gamma in terms of produtcs
               of gamma and rising factorial with integer, absolute
               constant exponent.

           (3) Perform simplification of nested fractions, powers
               and if the resulting expression is a quotient of
               polynomials, reduce their total degree.

       If the term given is hypergeometric then the result of this
       procudure is a quotient of polynomials of minimal degree.
       Sequence is hypergeometric if it is anihilated by linear,
       homogeneous recurrence operator of first order, so in
       other words when a(n+1)/a(n) is a rational function.

       When the status of being hypergeometric or not, is required
       then you can avoid additional simplification by unsetting
       'simplify' flag.

       This algorithm, due to Wolfram Koepf, is very simple but
       powerful, however its full potential will be visible when
       simplification in general will improve.

       For more information on the implemented algorithm refer to:

       [1] W. Koepf, Algorithms for m-fold Hypergeometric Summation,
           Journal of Symbolic Computation (1995) 20, 399-417
    """
    term = Basic.sympify(term)

    if consecutive == True:
        term = term.subs(n, n+1)/term

    expr = term.rewrite(gamma).expand(func=True, basic=False)

    p, q = together(expr).as_numer_denom()

    if p.is_polynomial(n) and q.is_polynomial(n):
        if simplify == True:
            from sympy.polynomials import gcd, quo

            G = gcd(p, q, n)

            if not isinstance(G, Basic.One):
                p = quo(p, G, n)
                q = quo(q, G, n)

                p = p.as_polynomial(n)
                q = q.as_polynomial(n)

                a, p = p.as_integer()
                b, q = q.as_integer()

                p = p.as_basic()
                q = q.as_basic()

                return (b/a) * (p/q)

        return p/q
    else:
        return None
Exemple #8
0
def rsolve_hyper(coeffs, f, n, **hints):
    """Given linear recurrence operator L of order 'k' with polynomial
       coefficients and inhomogeneous equation Ly = f we seek for all
       hypergeometric solutions over field K of characteristic zero.

       The inhomogeneous part can be either hypergeometric or a sum
       of a fixed number of pairwise dissimilar hypergeometric terms.

       The algorithm performs three basic steps:

           (1) Group together similar hypergeometric terms in the
               inhomogeneous part of Ly = f, and find particular
               solution using Abramov's algorithm.

           (2) Compute generating set of L and find basis in it,
               so that all solutions are lineary independent.

           (3) Form final solution with the number of arbitrary
               constants equal to dimension of basis of L.

       Term a(n) is hypergeometric if it is anihilated by first order
       linear difference equations with polynomial coefficients or, in
       simpler words, if consecutive term ratio is a rational function.

       The output of this procedure is a linear combination of fixed
       number of hypergeometric terms. However the underlying method
       can generate larger class of solutions - D'Alembertian terms.

       Note also that this method not only computes the kernel of the
       inhomogeneous equation, but also reduces in to a basis so that
       solutions generated by this procedure are lineary independent

       For more information on the implemented algorithm refer to:

       [1] M. Petkovsek, Hypergeometric solutions of linear recurrences
           with polynomial coefficients, J. Symbolic Computation,
           14 (1992), 243-264.

       [2] M. Petkovsek, H. S. Wilf, D. Zeilberger, A = B, 1996.

    """
    coeffs = map(Basic.sympify, coeffs)

    f = Basic.sympify(f)

    r, kernel = len(coeffs)-1, []

    if not isinstance(f, Basic.Zero):
        if isinstance(f, Basic.Add):
            similar = {}

            for g in f.expand():
                if not g.is_hypergeometric(n):
                    return None

                for h in similar.iterkeys():
                    if hypersimilar(g, h, n):
                        similar[h] += g
                        break
                else:
                    similar[g] = S.Zero

            inhomogeneous = []

            for g, h in similar.iteritems():
                inhomogeneous.append(g+h)
        elif f.is_hypergeometric(n):
            inhomogeneous = [f]
        else:
            return None

        for i, g in enumerate(inhomogeneous):
            coeff, polys = S.One, coeffs[:]
            denoms = [ S.One ] * (r+1)

            s = hypersimp(g, n)

            for j in xrange(1, r+1):
                coeff *= s.subs(n, n+j-1)

                p, q = coeff.as_numer_denom()

                polys[j] *= p
                denoms[j] = q

            for j in xrange(0, r+1):
                polys[j] *= Mul(*(denoms[:j] + denoms[j+1:]))

            R = rsolve_poly(polys, Mul(*denoms), n)

            if not (R is None or isinstance(R, Basic.Zero)):
                inhomogeneous[i] *= R
            else:
                return None

            result = Add(*inhomogeneous)
    else:
        result = S.Zero

    Z = Symbol('Z', dummy=True)

    p, q = coeffs[0], coeffs[r].subs(n, n-r+1)

    p_factors = [ z for z in set(roots(p, n)) ]
    q_factors = [ z for z in set(roots(q, n)) ]

    factors = [ (S.One, S.One) ]

    for p in p_factors:
        for q in q_factors:
            if p.is_integer and q.is_integer and p <= q:
                continue
            else:
                factors += [(n-p, n-q)]

    p = [ (n-p, S.One) for p in p_factors ]
    q = [ (S.One, n-q) for q in q_factors ]

    factors = p + factors + q

    for A, B in factors:
        polys, degrees = [], []
        D = A*B.subs(n, n+r-1)

        for i in xrange(0, r+1):
            a = Mul(*[ A.subs(n, n+j) for j in xrange(0, i) ])
            b = Mul(*[ B.subs(n, n+j) for j in xrange(i, r) ])

            poly = (quo(coeffs[i]*a*b, D, n))
            polys.append(poly.as_polynomial(n))

            if not isinstance(poly, Basic.Zero):
                degrees.append(polys[i].degree())

        d, poly = max(degrees), S.Zero

        for i in xrange(0, r+1):
            coeff = polys[i].nth_coeff(d)

            if not isinstance(coeff, Basic.Zero):
                poly += coeff * Z**i

        for z in set(roots(poly, Z)):
            if not z.is_real or z.is_zero:
                continue

            C = rsolve_poly([ polys[i]*z**i for i in xrange(r+1) ], 0, n)

            if C is not None and not isinstance(C, Basic.Zero):
                ratio = z * A * C.subs(n, n + 1) / B / C
                K = product(simplify(ratio), (n, 0, n-1))

                if casoratian(kernel+[K], n) != 0:
                    kernel.append(K)

    symbols = [ Symbol('C'+str(i)) for i in xrange(len(kernel)) ]

    for C, ker in zip(symbols, kernel):
        result += C * ker

    if hints.get('symbols', False):
        return (result, symbols)
    else:
        return result
Exemple #9
0
def rsolve_ratio(coeffs, f, n, **hints):
    """Given linear recurrence operator L of order 'k' with polynomial
       coefficients and inhomogeneous equation Ly = f, where 'f' is a
       polynomial, we seek for all rational solutions over field K of
       characteristic zero.

       This procedure accepts only polynomials, however if you are
       interested in solving recurrence with ratinal coefficients
       then use rsolve() with will preprocess equation given and
       run this procedure with polynomial arguments.

       The algorithm performs two basic steps:

           (1) Compute polynomial v(n) which can be used as universal
               denominator of any rational solution of equation Ly = f.

           (2) Construct new linear difference equation by substitution
               y(n) = u(n)/v(n) and solve it for u(n) finding all its
               polynomial solutions. Return None if none were found.

       Algorithm implemented here is a revised version of the original
       Abramov's algorithm, developed in 1989. The new approach is much
       simpler to implement and has better overall efficiency. This
       method can be easily adapted to q-difference equations case.

       Besides finding rational solutions alone, this functions is
       an important part of Hyper algorithm were it is used to find
       particular solution of ingomogeneous part of a recurrence.

       For more information on the implemented algorithm refer to:

       [1] S. A. Abramov, Rational solutions of linear difference
           and q-difference equations with polynomial coefficients,
           in: T. Levelt, ed., Proc. ISSAC '95, ACM Press, New York,
           1995, 285-289

    """
    f = Basic.sympify(f)

    if not f.is_polynomial(n):
        return None

    coeffs = map(Basic.sympify, coeffs)

    r = len(coeffs)-1

    A, B = coeffs[r], coeffs[0]
    A = A.subs(n, n-r).expand()

    h = Symbol('h', dummy=True)

    res = resultant(A, B.subs(n, n+h), n)

    if not res.is_polynomial(h):
        p, q = res.as_numer_denom()
        res = quo(p, q, h)

    _nni_roots = nni_roots(res, h)

    if _nni_roots == []:
        return rsolve_poly(coeffs, f, n, **hints)
    else:
        C, numers = S.One, [S.Zero]*(r+1)

        for i in xrange(int(max(_nni_roots)), -1, -1):
            d = gcd(A, B.subs(n, n+i), n)

            A = quo(A, d, n)
            B = quo(B, d.subs(n, n-i), n)

            C *= Mul(*[ d.subs(n, n-j) for j in xrange(0, i+1) ])

        denoms = [ C.subs(n, n+i) for i in range(0, r+1) ]

        for i in range(0, r+1):
            g = gcd(coeffs[i], denoms[i], n)

            numers[i] = quo(coeffs[i], g, n)
            denoms[i] = quo(denoms[i], g, n)

        for i in xrange(0, r+1):
            numers[i] *= Mul(*(denoms[:i] + denoms[i+1:]))

        result = rsolve_poly(numers, f * Mul(*denoms), n, **hints)

        if result is not None:
            if hints.get('symbols', False):
                return (simplify(result[0] / C), result[1])
            else:
                return simplify(result / C)
        else:
            return None
Exemple #10
0
def apart(f, z, domain=None, index=None):
    """Computes full partial fraction decomposition of a univariate
       rational function over the algebraic closure of its field of
       definition. Although only gcd operations over the initial
       field are required, the expansion is returned in a formal
       form with linear denominators.

       However it is possible to force expansion of the resulting
       formal summations, and so factorization over a specified
       domain is performed.

       To specify the desired behavior of the algorithm use the
       'domain' keyword. Setting it to None, which is done be
       default, will result in no factorization at all.

       Otherwise it can be assigned with one of Z, Q, R, C domain
       specifiers and the formal partial fraction expansion will
       be rewritten using all possible roots over this domain.

       If the resulting expansion contains formal summations, then
       for all those a single dummy index variable named 'a' will
       be generated. To change this default behavior issue new
       name via 'index' keyword.

       For more information on the implemented algorithm refer to:

       [1] M. Bronstein, B. Salvy, Full partial fraction decomposition
           of rational functions, in: M. Bronstein, ed., Proceedings
           ISSAC '93, ACM Press, Kiev, Ukraine, 1993, pp. 157-160.

    """
    f = Basic.sympify(f)

    if isinstance(f, Basic.Add):
        return Add(*[ apart(g) for g in f ])
    else:
        if f.is_fraction(z):
            f = normal(f, z)
        else:
            return f

        P, Q = f.as_numer_denom()

        if not Q.has(z):
            return f

        u = Function('u')(z)

        if index is None:
            A = Symbol('a', dummy=True)
        else:
            A = Symbol(index)

        partial, r = div(P, Q, z)
        f, q, U = r / Q, Q, []

        for k, d in enumerate(sqf(q, z)):
            n, d = k + 1, d.as_basic()
            U += [ u.diff(z, k) ]

            h = normal(f * d**n, z) / u**n

            H, subs = [h], []

            for j in range(1, n):
                H += [ H[-1].diff(z) / j ]

            for j in range(1, n+1):
                subs += [ (U[j-1], d.diff(z, j) / j) ]

            for j in range(0, n):
                P, Q = together(H[j]).as_numer_denom()

                for i in range(0, j+1):
                    P = P.subs(*subs[j-i])

                Q = Q.subs(*subs[0])

                G = gcd(P, d, z)
                D = quo(d, G, z)

                g, B, _ = ext_gcd(Q, D, z)
                b = rem(P * B / g, D, z)

                term = b.subs(z, A) / (z - A)**(n-j)

                if domain is None:
                    a = D.diff(z)

                    if not a.has(z):
                        partial += term.subs(A, -D.subs(z, 0) / a)
                    else:
                        partial += Basic.Sum(term, (A, Basic.RootOf(D, z)))
                else:
                    raise NotImplementedError

        return partial