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_poly(k) A = B = Q = S.One C_= poly.LC all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a-r, n-a+1) Q *= n - r if len(all_roots) < poly.degree: B = Product(quo(poly, Q.as_poly(k)), (k, a, n)) return poly.LC**(n-a+1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p) q = self._eval_product(q) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: 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 term.is_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
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_poly(k) A = B = Q = S.One C_ = poly.LC all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a - r, n - a + 1) Q *= n - r if len(all_roots) < poly.degree: B = Product(quo(poly, Q.as_poly(k)), (k, a, n)) return poly.LC**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p) q = self._eval_product(q) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: 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 term.is_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
def _eval_product(self, a, n, term): from sympy import summation, Sum k = self.index if not term.has(k): return term**(n-a+1) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One C_= poly.LC() all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a-r, n-a+1) Q *= n - r if len(all_roots) < poly.degree(): B = Product(quo(poly, Q.as_poly(k)), (k, a, n)) return poly.LC()**(n-a+1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(a, n, p) q = self._eval_product(a, n, q) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(a, n, t) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: A, B = Mul(*exclude), term._new_rawargs(*include) return A * Product(B, (k, a, n)) elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) if not isinstance(s, Sum): return term.base**s elif not term.exp.has(k): p = self._eval_product(a, n, term.base) if p is not None: return p**term.exp
def _eval_product(self, a, n, term): from sympy import summation, Sum k = self.index if not term.has(k): return term**(n - a + 1) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One C_ = poly.LC() all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a - r, n - a + 1) Q *= n - r if len(all_roots) < poly.degree(): B = Product(quo(poly, Q.as_poly(k)), (k, a, n)) return poly.LC()**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(a, n, p) q = self._eval_product(a, n, q) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(a, n, t) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: A, B = Mul(*exclude), term._new_rawargs(*include) return A * Product(B, (k, a, n)) elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) if not isinstance(s, Sum): return term.base**s elif not term.exp.has(k): p = self._eval_product(a, n, term.base) if p is not None: return p**term.exp
def extract(f): p = f.args[0] for q in f.args[1:]: p = gcd(p, q, *symbols) if p.is_number: return S.One, f return p, Add(*[quo(g, p, *symbols) for g in f.args])
def _splitter(p): for y in V: if not p.has(y): continue if _derivation(y) is not S.Zero: c, q = p.as_poly(y).primitive() q = q.as_expr() h = gcd(q, _derivation(q), y) s = quo(h, gcd(q, q.diff(y), y), y) c_split = _splitter(c) if s.as_poly(y).degree() == 0: return (c_split[0], q * c_split[1]) q_split = _splitter(cancel(q / s)) return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) return (S.One, p)
def splitter(p): for y in V: if not p.has_any_symbols(y): continue if derivation(y) is not S.Zero: c, q = p.as_poly(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_poly(y).degree == 0: return (c_split[0], q * c_split[1]) q_split = splitter(Poly.cancel((q, s), *V)) return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) else: return (S.One, p)
def splitter(p): for y in V: if not p.has_any_symbols(y): continue if derivation(y) is not S.Zero: c, q = p.as_poly(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_poly(y).degree == 0: return (c_split[0], q * c_split[1]) q_split = splitter(Poly.cancel((q, s), *V)) return (c_split[0] * q_split[0] * s, c_split[1] * q_split[1]) else: return (S.One, p)
def _splitter(p): for y in V: if not p.has(y): continue if _derivation(y) is not S.Zero: c, q = p.as_poly(y).primitive() q = q.as_expr() h = gcd(q, _derivation(q), y) s = quo(h, gcd(q, q.diff(y), y), y) c_split = _splitter(c) if s.as_poly(y).degree() == 0: return (c_split[0], q * c_split[1]) q_split = _splitter(cancel(q / s)) return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) else: return (S.One, p)
def rsolve(f, y, init=None): """ Solve univariate recurrence with rational coefficients. Given `k`-th order linear recurrence `\operatorname{L} y = f`, or equivalently: .. math:: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) + \dots + a_{0}(n) y(n) = f(n) where `a_{i}(n)`, for `i=0, \dots, k`, are polynomials or rational functions in `n`, and `f` is a hypergeometric function or a sum of a fixed number of pairwise dissimilar hypergeometric terms in `n`, finds all solutions or returns ``None``, if none were found. Initial conditions can be given as a dictionary in two forms: (1) ``{ n_0 : v_0, n_1 : v_1, ..., n_m : v_m }`` (2) ``{ y(n_0) : v_0, y(n_1) : v_1, ..., y(n_m) : v_m }`` or as a list ``L`` of values: ``L = [ v_0, v_1, ..., v_m ]`` where ``L[i] = v_i``, for `i=0, \dots, m`, maps to `y(n_i)`. Examples ======== Lets consider the following recurrence: .. math:: (n - 1) y(n + 2) - (n^2 + 3 n - 2) y(n + 1) + 2 n (n + 1) y(n) = 0 >>> from sympy import Function, rsolve >>> from sympy.abc import n >>> y = Function('y') >>> f = (n - 1)*y(n + 2) - (n**2 + 3*n - 2)*y(n + 1) + 2*n*(n + 1)*y(n) >>> rsolve(f, y(n)) 2**n*C0 + C1*factorial(n) >>> rsolve(f, y(n), { y(0):0, y(1):3 }) 3*2**n - 3*factorial(n) See Also ======== rsolve_poly, rsolve_ratio, rsolve_hyper """ if isinstance(f, Equality): f = f.lhs - f.rhs n = y.args[0] k = Wild('k', exclude=(n,)) # Preprocess user input to allow things like # y(n) + a*(y(n + 1) + y(n - 1))/2 f = f.expand().collect(y.func(Wild('m', integer=True))) h_part = defaultdict(lambda: S.Zero) i_part = S.Zero for g in Add.make_args(f): coeff = S.One kspec = None for h in Mul.make_args(g): if h.is_Function: if h.func == y.func: result = h.args[0].match(n + k) if result is not None: kspec = int(result[k]) else: raise ValueError( "'%s(%s+k)' expected, got '%s'" % (y.func, n, h)) else: raise ValueError( "'%s' expected, got '%s'" % (y.func, h.func)) else: coeff *= h if kspec is not None: h_part[kspec] += coeff else: i_part += coeff for k, coeff in h_part.iteritems(): h_part[k] = simplify(coeff) common = S.One for coeff in h_part.itervalues(): if coeff.is_rational_function(n): if not coeff.is_polynomial(n): common = lcm(common, coeff.as_numer_denom()[1], n) else: raise ValueError( "Polynomial or rational function expected, got '%s'" % coeff) i_numer, i_denom = i_part.as_numer_denom() if i_denom.is_polynomial(n): common = lcm(common, i_denom, n) if common is not S.One: for k, coeff in h_part.iteritems(): numer, denom = coeff.as_numer_denom() h_part[k] = numer*quo(common, denom, n) i_part = i_numer*quo(common, i_denom, n) K_min = min(h_part.keys()) if K_min < 0: K = abs(K_min) H_part = defaultdict(lambda: S.Zero) i_part = i_part.subs(n, n + K).expand() common = common.subs(n, n + K).expand() for k, coeff in h_part.iteritems(): H_part[k + K] = coeff.subs(n, n + K).expand() else: H_part = h_part K_max = max(H_part.iterkeys()) coeffs = [H_part[i] for i in xrange(K_max + 1)] result = rsolve_hyper(coeffs, -i_part, n, symbols=True) if result is None: return None solution, symbols = result if init == {} or init == []: init = None if symbols and init is not None: if type(init) is list: init = dict([(i, init[i]) for i in xrange(len(init))]) equations = [] for k, v in init.iteritems(): try: i = int(k) except TypeError: if k.is_Function and k.func == y.func: i = int(k.args[0]) else: raise ValueError("Integer or term expected, got '%s'" % k) try: eq = solution.limit(n, i) - v except NotImplementedError: eq = solution.subs(n, i) - v equations.append(eq) result = solve(equations, *symbols) if not result: return None else: solution = solution.subs(result) return solution
def rsolve_hyper(coeffs, f, n, **hints): """ Given linear recurrence operator `\operatorname{L}` of order `k` with polynomial coefficients and inhomogeneous equation `\operatorname{L} y = 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 `\operatorname{L} y = f`, and find particular solution using Abramov's algorithm. (2) Compute generating set of `\operatorname{L}` and find basis in it, so that all solutions are linearly independent. (3) Form final solution with the number of arbitrary constants equal to dimension of basis of `\operatorname{L}`. Term `a(n)` is hypergeometric if it is annihilated 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 linearly independent Examples ======== >>> from sympy.solvers import rsolve_hyper >>> from sympy.abc import x >>> rsolve_hyper([-1, -1, 1], 0, x) C0*(1/2 + sqrt(5)/2)**x + C1*(-sqrt(5)/2 + 1/2)**x >>> rsolve_hyper([-1, 1], 1 + x, x) C0 + x*(x + 1)/2 References ========== .. [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(sympify, coeffs) f = sympify(f) r, kernel, symbols = len(coeffs) - 1, [], set() if not f.is_zero: if f.is_Add: similar = {} for g in f.expand().args: 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 R is S.Zero): inhomogeneous[i] *= R else: return None result = Add(*inhomogeneous) else: result = S.Zero Z = Dummy('Z') p, q = coeffs[0], coeffs[r].subs(n, n - r + 1) p_factors = [ z for z in roots(p, n).iterkeys() ] q_factors = [ z for z in roots(q, n).iterkeys() ] 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_poly(n)) if not poly.is_zero: degrees.append(polys[i].degree()) d, poly = max(degrees), S.Zero for i in xrange(0, r + 1): coeff = polys[i].nth(d) if coeff is not S.Zero: poly += coeff * Z**i for z in roots(poly, Z).iterkeys(): if z.is_zero: continue (C, s) = rsolve_poly([ polys[i]*z**i for i in xrange(r + 1) ], 0, n, symbols=True) if C is not None and C is not S.Zero: symbols |= set(s) ratio = z * A * C.subs(n, n + 1) / B / C ratio = simplify(ratio) # If there is a nonnegative root in the denominator of the ratio, # this indicates that the term y(n_root) is zero, and one should # start the product with the term y(n_root + 1). n0 = 0 for n_root in roots(ratio.as_numer_denom()[1], n).keys(): if (n0 < (n_root + 1)) is True: n0 = n_root + 1 K = product(ratio, (n, n0, n - 1)) if K.has(factorial, FallingFactorial, RisingFactorial): K = simplify(K) if casoratian(kernel + [K], n, zero=False) != 0: kernel.append(K) kernel.sort(key=default_sort_key) sk = zip(numbered_symbols('C'), kernel) if sk: for C, ker in sk: result += C * ker else: return None if hints.get('symbols', False): symbols |= set([s for s, k in sk]) return (result, list(symbols)) else: return result
def rsolve_ratio(coeffs, f, n, **hints): """ Given linear recurrence operator `\operatorname{L}` of order `k` with polynomial coefficients and inhomogeneous equation `\operatorname{L} y = 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 rational coefficients then use ``rsolve`` which will pre-process the given equation 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 `\operatorname{L} y = 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 inhomogeneous part of a recurrence. Examples ======== >>> from sympy.abc import x >>> from sympy.solvers.recurr import rsolve_ratio >>> rsolve_ratio([-2*x**3 + x**2 + 2*x - 1, 2*x**3 + x**2 - 6*x, ... - 2*x**3 - 11*x**2 - 18*x - 9, 2*x**3 + 13*x**2 + 22*x + 8], 0, x) C2*(2*x - 3)/(2*(x**2 - 1)) References ========== .. [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 See Also ======== rsolve_hyper """ f = sympify(f) if not f.is_polynomial(n): return None coeffs = map(sympify, coeffs) r = len(coeffs) - 1 A, B = coeffs[r], coeffs[0] A = A.subs(n, n - r).expand() h = Dummy('h') 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 = roots(res, h, filter='Z', predicate=lambda r: r >= 0).keys() if not 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
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.functions import KroneckerDelta, RisingFactorial (k, a, n) = limits if k not in term.free_symbols: if (term - 1).is_zero: return S.One return term**(n - a + 1) if a == n: return term.subs(k, a) if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]): return deltaproduct(term, limits) dif = n - a if dif.is_Integer: return Mul(*[term.subs(k, a + i) for i in range(dif + 1)]) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly) M = 0 for r, m in all_roots.items(): M += m A *= RisingFactorial(a - r, n - a + 1)**m Q *= (n - r)**m if M < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = self.func(arg, (k, a, n)).doit() return poly.LC()**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p, (k, a, n)) q = self._eval_product(q, (k, a, n)) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = self.func(arg, (k, a, n)).doit() return A * B elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base, (k, a, n)) if p is not None: return p**term.exp elif isinstance(term, Product): evaluated = term.doit() f = self._eval_product(evaluated, limits) if f is None: return self.func(evaluated, limits) else: return f
def _eval_product(self, term, limits): from sympy import summation (k, a, n) = limits if k not in term.free_symbols: return term**(n - a + 1) if a == n: return term.subs(k, a) dif = n - a if dif.is_Integer: return Mul(*[term.subs(k, a + i) for i in xrange(dif + 1)]) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a - r, n - a + 1) Q *= n - r if len(all_roots) < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = Product(arg, (k, a, n)).doit() return poly.LC()**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p, (k, a, n)) q = self._eval_product(q, (k, a, n)) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = Product(arg, (k, a, n)).doit() return A * B elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base, (k, a, n)) if p is not None: return p**term.exp elif isinstance(term, Product): evaluated = term.doit() f = self._eval_product(evaluated, limits) if f is None: return Product(evaluated, limits) else: return f
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.functions import KroneckerDelta, RisingFactorial (k, a, n) = limits if k not in term.free_symbols: if (term - 1).is_zero: return S.One return term**(n - a + 1) if a == n: return term.subs(k, a) if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]): return deltaproduct(term, limits) dif = n - a if dif.is_Integer: return Mul(*[term.subs(k, a + i) for i in range(dif + 1)]) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly) M = 0 for r, m in all_roots.items(): M += m A *= RisingFactorial(a - r, n - a + 1)**m Q *= (n - r)**m if M < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = self.func(arg, (k, a, n)).doit() return poly.LC()**(n - a + 1) * A * B elif term.is_Add: factored = factor_terms(term, fraction=True) if factored.is_Mul: return self._eval_product(factored, (k, a, n)) elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = self.func(arg, (k, a, n)).doit() return A * B elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base, (k, a, n)) if p is not None: return p**term.exp elif isinstance(term, Product): evaluated = term.doit() f = self._eval_product(evaluated, limits) if f is None: return self.func(evaluated, limits) else: return f
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.functions import KroneckerDelta, RisingFactorial (k, a, n) = limits if k not in term.free_symbols: if (term - 1).is_zero: return S.One return term**(n - a + 1) if a == n: return term.subs(k, a) if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]): return deltaproduct(term, limits) dif = n - a definite = dif.is_Integer if definite and (dif < 100): return self._eval_product_direct(term, limits) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly) M = 0 for r, m in all_roots.items(): M += m A *= RisingFactorial(a - r, n - a + 1)**m Q *= (n - r)**m if M < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = self.func(arg, (k, a, n)).doit() return poly.LC()**(n - a + 1) * A * B elif term.is_Add: factored = factor_terms(term, fraction=True) if factored.is_Mul: return self._eval_product(factored, (k, a, n)) elif term.is_Mul: # Factor in part without the summation variable and part with without_k, with_k = term.as_coeff_mul(k) if len(with_k) >= 2: # More than one term including k, so still a multiplication exclude, include = [], [] for t in with_k: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = self.func(arg, (k, a, n)).doit() return without_k**(n - a + 1) * A * B else: # Just a single term p = self._eval_product(with_k[0], (k, a, n)) if p is None: p = self.func(with_k[0], (k, a, n)).doit() return without_k**(n - a + 1) * p elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base, (k, a, n)) if p is not None: return p**term.exp elif isinstance(term, Product): evaluated = term.doit() f = self._eval_product(evaluated, limits) if f is None: return self.func(evaluated, limits) else: return f if definite: return self._eval_product_direct(term, limits)
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.functions import KroneckerDelta, RisingFactorial (k, a, n) = limits if k not in term.free_symbols: if (term - 1).is_zero: return S.One return term**(n - a + 1) if a == n: return term.subs(k, a) if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]): return deltaproduct(term, limits) dif = n - a if dif.is_Integer: return Mul(*[term.subs(k, a + i) for i in range(dif + 1)]) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly) M = 0 for r, m in all_roots.items(): M += m A *= RisingFactorial(a - r, n - a + 1)**m Q *= (n - r)**m if M < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = self.func(arg, (k, a, n)).doit() return poly.LC()**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() q = self._eval_product(q, (k, a, n)) if q.is_Number: # There is expression, which couldn't change by # as_numer_denom(). E.g. n**(2/3) + 1 --> (n**(2/3) + 1, 1). # We have to catch this case. p = sum([self._eval_product(i, (k, a, n)) for i in p.as_coeff_Add()]) else: p = self._eval_product(p, (k, a, n)) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = self.func(arg, (k, a, n)).doit() return A * B elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base, (k, a, n)) if p is not None: return p**term.exp elif isinstance(term, Product): evaluated = term.doit() f = self._eval_product(evaluated, limits) if f is None: return self.func(evaluated, limits) else: return f
def normal(f, g, n=None): """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 following 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 a tuple containing elements of this factorization in the form (Z*A, B, C). For example: >>> from sympy import Symbol, normal >>> 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(sympify, (f, g)) p = f.as_poly(n, field=True) q = g.as_poly(n, field=True) a, p = p.LC(), p.monic() b, q = q.LC(), q.monic() A = p.as_basic() B = q.as_basic() C, Z = S.One, a / b h = Dummy('h') res = resultant(A, B.subs(n, n+h), n) nni_roots = roots(res, h, filter='Z', predicate=lambda r: r >= 0).keys() if not nni_roots: return (f, g, S.One) else: for i in sorted(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)
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(sympify, coeffs) f = sympify(f) r, kernel = len(coeffs) - 1, [] if not f.is_zero: if f.is_Add: similar = {} for g in f.expand().args: 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 R is S.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 roots(p, n).iterkeys()] q_factors = [z for z in roots(q, n).iterkeys()] 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_poly(n)) if not poly.is_zero: degrees.append(polys[i].degree) d, poly = max(degrees), S.Zero for i in xrange(0, r + 1): coeff = polys[i].coeff(d) if coeff is not S.Zero: poly += coeff * Z**i for z in roots(poly, Z).iterkeys(): 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 C is not S.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
def rsolve(f, y, init=None): """Solve univariate recurrence with rational coefficients. Given k-th order linear recurrence Ly = f, or equivalently: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) + ... + a_{0}(n) y(n) = f where a_{i}(n), for i=0..k, are polynomials or rational functions in n, and f is a hypergeometric function or a sum of a fixed number of pairwise dissimilar hypergeometric terms in n, finds all solutions or returns None, if none were found. Initial conditions can be given as a dictionary in two forms: [1] { n_0 : v_0, n_1 : v_1, ..., n_m : v_m } [2] { y(n_0) : v_0, y(n_1) : v_1, ..., y(n_m) : v_m } or as a list L of values: L = [ v_0, v_1, ..., v_m ] where L[i] = v_i, for i=0..m, maps to y(n_i). As an example lets consider the following recurrence: (n - 1) y(n + 2) - (n**2 + 3 n - 2) y(n + 1) + 2 n (n + 1) y(n) == 0 >>> from sympy import Function, rsolve >>> from sympy.abc import n >>> y = Function('y') >>> f = (n-1)*y(n+2) - (n**2+3*n-2)*y(n+1) + 2*n*(n+1)*y(n) >>> rsolve(f, y(n)) C0*gamma(1 + n) + C1*2**n >>> rsolve(f, y(n), { y(0):0, y(1):3 }) -3*gamma(1 + n) + 3*2**n """ if isinstance(f, Equality): f = f.lhs - f.rhs if f.is_Add: F = f.args else: F = [f] k = Wild('k') n = y.args[0] h_part = {} i_part = S.Zero for g in F: if g.is_Mul: G = g.args else: G = [g] coeff = S.One kspec = None for h in G: if h.is_Function: if h.func == y.func: result = h.args[0].match(n + k) if result is not None: kspec = int(result[k]) else: raise ValueError("'%s(%s+k)' expected, got '%s'" % (y.func, n, h)) else: raise ValueError("'%s' expected, got '%s'" % (y.func, h.func)) else: coeff *= h if kspec is not None: if kspec in h_part: h_part[kspec] += coeff else: h_part[kspec] = coeff else: i_part += coeff for k, coeff in h_part.iteritems(): h_part[k] = simplify(coeff) common = S.One for coeff in h_part.itervalues(): if coeff.is_rational_function(n): if not coeff.is_polynomial(n): common = lcm(common, coeff.as_numer_denom()[1], n) else: raise ValueError("Polynomial or rational function expected, got '%s'" % coeff) i_numer, i_denom = i_part.as_numer_denom() if i_denom.is_polynomial(n): common = lcm(common, i_denom, n) if common is not S.One: for k, coeff in h_part.iteritems(): numer, denom = coeff.as_numer_denom() h_part[k] = numer*quo(common, denom, n) i_part = i_numer*quo(common, i_denom, n) K_min = min(h_part.keys()) if K_min < 0: K = abs(K_min) H_part = {} i_part = i_part.subs(n, n+K).expand() common = common.subs(n, n+K).expand() for k, coeff in h_part.iteritems(): H_part[k+K] = coeff.subs(n, n+K).expand() else: H_part = h_part K_max = max(H_part.keys()) coeffs = [] for i in xrange(0, K_max+1): if i in H_part: coeffs.append(H_part[i]) else: coeffs.append(S.Zero) result = rsolve_hyper(coeffs, i_part, n, symbols=True) if result is None: return None else: solution, symbols = result if symbols and init is not None: equations = [] if type(init) is list: for i in xrange(0, len(init)): eq = solution.subs(n, i) - init[i] equations.append(eq) else: for k, v in init.iteritems(): try: i = int(k) except TypeError: if k.is_Function and k.func == y.func: i = int(k.args[0]) else: raise ValueError("Integer or term expected, got '%s'" % k) eq = solution.subs(n, i) - v equations.append(eq) result = solve(equations, *symbols) if result is None: return None else: for k, v in result.iteritems(): solution = solution.subs(k, v) return (solution.expand()) / common
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 linearly 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 annihilated 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 linearly 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(sympify, coeffs) f = sympify(f) r, kernel = len(coeffs)-1, [] if not f.is_zero: if f.is_Add: similar = {} for g in f.expand().args: 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 R is S.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 roots(p, n).iterkeys() ] q_factors = [ z for z in roots(q, n).iterkeys() ] 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_poly(n)) if not poly.is_zero: degrees.append(polys[i].degree) d, poly = max(degrees), S.Zero for i in xrange(0, r+1): coeff = polys[i].coeff(d) if coeff is not S.Zero: poly += coeff * Z**i for z in roots(poly, Z).iterkeys(): 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 C is not S.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
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 rational coefficients then use rsolve() which will pre-process the given equation 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 inhomogeneous 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 = sympify(f) if not f.is_polynomial(n): return None coeffs = map(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 = roots(res, h, domain='Z', predicate=lambda r: r >= 0).keys() if not 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
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 = sympify(f) if not f.is_polynomial(n): return None coeffs = map(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 = roots(res, h, domain='Z', predicate=lambda r: r >= 0).keys() if not 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
def rsolve(f, y, init=None): """ Solve univariate recurrence with rational coefficients. Given k-th order linear recurrence Ly = f, or equivalently: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) + ... + a_{0}(n) y(n) = f where a_{i}(n), for i=0..k, are polynomials or rational functions in n, and f is a hypergeometric function or a sum of a fixed number of pairwise dissimilar hypergeometric terms in n, finds all solutions or returns None, if none were found. Initial conditions can be given as a dictionary in two forms: [1] { n_0 : v_0, n_1 : v_1, ..., n_m : v_m } [2] { y(n_0) : v_0, y(n_1) : v_1, ..., y(n_m) : v_m } or as a list L of values: L = [ v_0, v_1, ..., v_m ] where L[i] = v_i, for i=0..m, maps to y(n_i). As an example lets consider the following recurrence: (n - 1) y(n + 2) - (n**2 + 3 n - 2) y(n + 1) + 2 n (n + 1) y(n) == 0 >>> from sympy import Function, rsolve >>> from sympy.abc import n >>> y = Function('y') >>> f = (n-1)*y(n+2) - (n**2+3*n-2)*y(n+1) + 2*n*(n+1)*y(n) >>> rsolve(f, y(n)) 2**n*C1 + C0*n! >>> rsolve(f, y(n), { y(0):0, y(1):3 }) 3*2**n - 3*n! """ if isinstance(f, Equality): f = f.lhs - f.rhs k = Wild('k') n = y.args[0] h_part = defaultdict(lambda: S.Zero) i_part = S.Zero for g in Add.make_args(f): coeff = S.One kspec = None for h in Mul.make_args(g): if h.is_Function: if h.func == y.func: result = h.args[0].match(n + k) if result is not None: kspec = int(result[k]) else: raise ValueError("'%s(%s+k)' expected, got '%s'" % (y.func, n, h)) else: raise ValueError("'%s' expected, got '%s'" % (y.func, h.func)) else: coeff *= h if kspec is not None: h_part[kspec] += coeff else: i_part += coeff for k, coeff in h_part.iteritems(): h_part[k] = simplify(coeff) common = S.One for coeff in h_part.itervalues(): if coeff.is_rational_function(n): if not coeff.is_polynomial(n): common = lcm(common, coeff.as_numer_denom()[1], n) else: raise ValueError( "Polynomial or rational function expected, got '%s'" % coeff) i_numer, i_denom = i_part.as_numer_denom() if i_denom.is_polynomial(n): common = lcm(common, i_denom, n) if common is not S.One: for k, coeff in h_part.iteritems(): numer, denom = coeff.as_numer_denom() h_part[k] = numer * quo(common, denom, n) i_part = i_numer * quo(common, i_denom, n) K_min = min(h_part.keys()) if K_min < 0: K = abs(K_min) H_part = defaultdict(lambda: S.Zero) i_part = i_part.subs(n, n + K).expand() common = common.subs(n, n + K).expand() for k, coeff in h_part.iteritems(): H_part[k + K] = coeff.subs(n, n + K).expand() else: H_part = h_part K_max = max(H_part.iterkeys()) coeffs = [H_part[i] for i in xrange(K_max + 1)] result = rsolve_hyper(coeffs, i_part, n, symbols=True) if result is None: return None solution, symbols = result if symbols and init is not None: equations = [] if type(init) is list: for i in xrange(0, len(init)): eq = solution.subs(n, i) - init[i] equations.append(eq) else: for k, v in init.iteritems(): try: i = int(k) except TypeError: if k.is_Function and k.func == y.func: i = int(k.args[0]) else: raise ValueError("Integer or term expected, got '%s'" % k) eq = solution.subs(n, i) - v equations.append(eq) result = solve(equations, *symbols) if result is None: return None else: for k, v in result.iteritems(): solution = solution.subs(k, v) return (solution.expand()) / common
def normal(f, g, n=None): """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 following 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 a tuple containing elements of this factorization in the form (Z*A, B, C). For example: >>> from sympy import Symbol, normal >>> 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(sympify, (f, g)) p = f.as_poly(n, field=True) q = g.as_poly(n, field=True) a, p = p.LC(), p.monic() b, q = q.LC(), q.monic() A = p.as_basic() B = q.as_basic() C, Z = S.One, a / b h = Symbol('h', dummy=True) res = resultant(A, B.subs(n, n + h), n) nni_roots = roots(res, h, filter='Z', predicate=lambda r: r >= 0).keys() if not nni_roots: return (f, g, S.One) else: for i in sorted(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)