123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834 |
- r"""
- This module is intended for solving recurrences or, in other words,
- difference equations. Currently supported are linear, inhomogeneous
- equations with polynomial or rational coefficients.
- The solutions are obtained among polynomials, rational functions,
- hypergeometric terms, or combinations of hypergeometric term which
- are pairwise dissimilar.
- ``rsolve_X`` functions were meant as a low level interface
- for ``rsolve`` which would use Mathematica's syntax.
- Given a recurrence relation:
- .. math:: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) +
- ... + a_{0}(n) y(n) = f(n)
- where `k > 0` and `a_{i}(n)` are polynomials in `n`. To use
- ``rsolve_X`` we need to put all coefficients in to a list ``L`` of
- `k+1` elements the following way:
- ``L = [a_{0}(n), ..., a_{k-1}(n), a_{k}(n)]``
- where ``L[i]``, for `i=0, \ldots, k`, maps to
- `a_{i}(n) y(n+i)` (`y(n+i)` is implicit).
- For example if we would like to compute `m`-th Bernoulli polynomial
- up to a constant (example was taken from rsolve_poly docstring),
- then we would use `b(n+1) - b(n) = m n^{m-1}` recurrence, which
- has solution `b(n) = B_m + C`.
- Then ``L = [-1, 1]`` and `f(n) = m n^(m-1)` and finally for `m=4`:
- >>> from sympy import Symbol, bernoulli, rsolve_poly
- >>> n = Symbol('n', integer=True)
- >>> rsolve_poly([-1, 1], 4*n**3, n)
- C0 + n**4 - 2*n**3 + n**2
- >>> bernoulli(4, n)
- n**4 - 2*n**3 + n**2 - 1/30
- For the sake of completeness, `f(n)` can be:
- [1] a polynomial -> rsolve_poly
- [2] a rational function -> rsolve_ratio
- [3] a hypergeometric function -> rsolve_hyper
- """
- from collections import defaultdict
- from sympy.core.singleton import S
- from sympy.core.numbers import Rational, I
- from sympy.core.symbol import Symbol, Wild, Dummy
- from sympy.core.relational import Equality
- from sympy.core.add import Add
- from sympy.core.mul import Mul
- from sympy.core.sorting import default_sort_key
- from sympy.core.sympify import sympify
- from sympy.simplify import simplify, hypersimp, hypersimilar # type: ignore
- from sympy.solvers import solve, solve_undetermined_coeffs
- from sympy.polys import Poly, quo, gcd, lcm, roots, resultant
- from sympy.functions import binomial, factorial, FallingFactorial, RisingFactorial
- from sympy.matrices import Matrix, casoratian
- from sympy.utilities.iterables import numbered_symbols
- def rsolve_poly(coeffs, f, n, shift=0, **hints):
- r"""
- 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 polynomial solutions over field `K` of characteristic zero.
- The algorithm performs two basic steps:
- (1) Compute degree `N` of the general polynomial solution.
- (2) Find all polynomials of degree `N` or less
- of `\operatorname{L} y = f`.
- There are two methods for computing the polynomial solutions.
- If the degree bound is relatively small, i.e. it's smaller than
- or equal to the order of the recurrence, then naive method of
- undetermined coefficients is being used. This gives system
- of algebraic equations with `N+1` unknowns.
- In the other case, the algorithm performs transformation of the
- initial equation to an equivalent one, for which the system of
- algebraic equations has only `r` indeterminates. This method is
- quite sophisticated (in comparison with the naive one) and was
- invented together by Abramov, Bronstein and Petkovsek.
- It is possible to generalize the algorithm implemented here to
- the case of linear q-difference and differential equations.
- Lets say that we would like to compute `m`-th Bernoulli polynomial
- up to a constant. For this we can use `b(n+1) - b(n) = m n^{m-1}`
- recurrence, which has solution `b(n) = B_m + C`. For example:
- >>> from sympy import Symbol, rsolve_poly
- >>> n = Symbol('n', integer=True)
- >>> rsolve_poly([-1, 1], 4*n**3, n)
- C0 + n**4 - 2*n**3 + n**2
- References
- ==========
- .. [1] S. A. Abramov, M. Bronstein and M. Petkovsek, On polynomial
- solutions of linear operator equations, in: T. Levelt, ed.,
- Proc. ISSAC '95, ACM Press, New York, 1995, 290-296.
- .. [2] M. Petkovsek, Hypergeometric solutions of linear recurrences
- with polynomial coefficients, J. Symbolic Computation,
- 14 (1992), 243-264.
- .. [3] M. Petkovsek, H. S. Wilf, D. Zeilberger, A = B, 1996.
- """
- f = sympify(f)
- if not f.is_polynomial(n):
- return None
- homogeneous = f.is_zero
- r = len(coeffs) - 1
- coeffs = [Poly(coeff, n) for coeff in coeffs]
- polys = [Poly(0, n)]*(r + 1)
- terms = [(S.Zero, S.NegativeInfinity)]*(r + 1)
- for i in range(r + 1):
- for j in range(i, r + 1):
- polys[i] += coeffs[j]*(binomial(j, i).as_poly(n))
- if not polys[i].is_zero:
- (exp,), coeff = polys[i].LT()
- terms[i] = (coeff, exp)
- d = b = terms[0][1]
- for i in range(1, r + 1):
- if terms[i][1] > d:
- d = terms[i][1]
- if terms[i][1] - i > b:
- b = terms[i][1] - i
- d, b = int(d), int(b)
- x = Dummy('x')
- degree_poly = S.Zero
- for i in range(r + 1):
- if terms[i][1] - i == b:
- degree_poly += terms[i][0]*FallingFactorial(x, i)
- nni_roots = list(roots(degree_poly, x, filter='Z',
- predicate=lambda r: r >= 0).keys())
- if nni_roots:
- N = [max(nni_roots)]
- else:
- N = []
- if homogeneous:
- N += [-b - 1]
- else:
- N += [f.as_poly(n).degree() - b, -b - 1]
- N = int(max(N))
- if N < 0:
- if homogeneous:
- if hints.get('symbols', False):
- return (S.Zero, [])
- else:
- return S.Zero
- else:
- return None
- if N <= r:
- C = []
- y = E = S.Zero
- for i in range(N + 1):
- C.append(Symbol('C' + str(i + shift)))
- y += C[i] * n**i
- for i in range(r + 1):
- E += coeffs[i].as_expr()*y.subs(n, n + i)
- solutions = solve_undetermined_coeffs(E - f, C, n)
- if solutions is not None:
- C = [c for c in C if (c not in solutions)]
- result = y.subs(solutions)
- else:
- return None # TBD
- else:
- A = r
- U = N + A + b + 1
- nni_roots = list(roots(polys[r], filter='Z',
- predicate=lambda r: r >= 0).keys())
- if nni_roots != []:
- a = max(nni_roots) + 1
- else:
- a = S.Zero
- def _zero_vector(k):
- return [S.Zero] * k
- def _one_vector(k):
- return [S.One] * k
- def _delta(p, k):
- B = S.One
- D = p.subs(n, a + k)
- for i in range(1, k + 1):
- B *= Rational(i - k - 1, i)
- D += B * p.subs(n, a + k - i)
- return D
- alpha = {}
- for i in range(-A, d + 1):
- I = _one_vector(d + 1)
- for k in range(1, d + 1):
- I[k] = I[k - 1] * (x + i - k + 1)/k
- alpha[i] = S.Zero
- for j in range(A + 1):
- for k in range(d + 1):
- B = binomial(k, i + j)
- D = _delta(polys[j].as_expr(), k)
- alpha[i] += I[k]*B*D
- V = Matrix(U, A, lambda i, j: int(i == j))
- if homogeneous:
- for i in range(A, U):
- v = _zero_vector(A)
- for k in range(1, A + b + 1):
- if i - k < 0:
- break
- B = alpha[k - A].subs(x, i - k)
- for j in range(A):
- v[j] += B * V[i - k, j]
- denom = alpha[-A].subs(x, i)
- for j in range(A):
- V[i, j] = -v[j] / denom
- else:
- G = _zero_vector(U)
- for i in range(A, U):
- v = _zero_vector(A)
- g = S.Zero
- for k in range(1, A + b + 1):
- if i - k < 0:
- break
- B = alpha[k - A].subs(x, i - k)
- for j in range(A):
- v[j] += B * V[i - k, j]
- g += B * G[i - k]
- denom = alpha[-A].subs(x, i)
- for j in range(A):
- V[i, j] = -v[j] / denom
- G[i] = (_delta(f, i - A) - g) / denom
- P, Q = _one_vector(U), _zero_vector(A)
- for i in range(1, U):
- P[i] = (P[i - 1] * (n - a - i + 1)/i).expand()
- for i in range(A):
- Q[i] = Add(*[(v*p).expand() for v, p in zip(V[:, i], P)])
- if not homogeneous:
- h = Add(*[(g*p).expand() for g, p in zip(G, P)])
- C = [Symbol('C' + str(i + shift)) for i in range(A)]
- g = lambda i: Add(*[c*_delta(q, i) for c, q in zip(C, Q)])
- if homogeneous:
- E = [g(i) for i in range(N + 1, U)]
- else:
- E = [g(i) + _delta(h, i) for i in range(N + 1, U)]
- if E != []:
- solutions = solve(E, *C)
- if not solutions:
- if homogeneous:
- if hints.get('symbols', False):
- return (S.Zero, [])
- else:
- return S.Zero
- else:
- return None
- else:
- solutions = {}
- if homogeneous:
- result = S.Zero
- else:
- result = h
- for c, q in list(zip(C, Q)):
- if c in solutions:
- s = solutions[c]*q
- C.remove(c)
- else:
- s = c*q
- result += s.expand()
- if hints.get('symbols', False):
- return (result, C)
- else:
- return result
- def rsolve_ratio(coeffs, f, n, **hints):
- r"""
- 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 = list(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 = list(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 range(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 range(i + 1)])
- denoms = [C.subs(n, n + i) for i in range(r + 1)]
- for i in range(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 range(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_hyper(coeffs, f, n, **hints):
- r"""
- 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*(1/2 + sqrt(5)/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.
- """
- from sympy.concrete import product
- coeffs = list(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.keys():
- if hypersimilar(g, h, n):
- similar[h] += g
- break
- else:
- similar[g] = S.Zero
- inhomogeneous = []
- for g, h in similar.items():
- 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 range(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 range(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).keys()]
- q_factors = [z for z in roots(q, n).keys()]
- 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 range(r + 1):
- a = Mul(*[A.subs(n, n + j) for j in range(i)])
- b = Mul(*[B.subs(n, n + j) for j in range(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())
- if degrees:
- d, poly = max(degrees), S.Zero
- else:
- return None
- for i in range(r + 1):
- coeff = polys[i].nth(d)
- if coeff is not S.Zero:
- poly += coeff * Z**i
- for z in roots(poly, Z).keys():
- if z.is_zero:
- continue
- recurr_coeffs = [polys[i].as_expr()*z**i for i in range(r + 1)]
- if d == 0 and 0 != Add(*[recurr_coeffs[j]*j for j in range(1, r + 1)]):
- # faster inline check (than calling rsolve_poly) for a
- # constant solution to a constant coefficient recurrence.
- C = Symbol("C" + str(len(symbols)))
- s = [C]
- else:
- C, s = rsolve_poly(recurr_coeffs, 0, n, len(symbols), 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 n_root.has(I):
- return None
- elif (n0 < (n_root + 1)) == 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 = list(zip(numbered_symbols('C'), kernel))
- if sk:
- for C, ker in sk:
- result += C * ker
- else:
- return None
- if hints.get('symbols', False):
- # XXX: This returns the symbols in a non-deterministic order
- symbols |= {s for s, k in sk}
- return (result, list(symbols))
- else:
- return result
- def rsolve(f, y, init=None):
- r"""
- 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) +
- \cdots + a_{0}(n) y(n) = f(n)
- where `a_{i}(n)`, for `i=0, \ldots, 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, \ldots, 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(list)
- i_part = []
- for g in Add.make_args(f):
- coeff, dep = g.as_coeff_mul(y.func)
- if not dep:
- i_part.append(coeff)
- continue
- for h in dep:
- if h.is_Function and h.func == y.func:
- result = h.args[0].match(n + k)
- if result is not None:
- h_part[int(result[k])].append(coeff)
- continue
- raise ValueError(
- "'%s(%s + k)' expected, got '%s'" % (y.func, n, h))
- for k in h_part:
- h_part[k] = Add(*h_part[k])
- h_part.default_factory = lambda: 0
- i_part = Add(*i_part)
- for k, coeff in h_part.items():
- h_part[k] = simplify(coeff)
- common = S.One
- if not i_part.is_zero and not i_part.is_hypergeometric(n) and \
- not (i_part.is_Add and all(map(lambda x: x.is_hypergeometric(n), i_part.expand().args))):
- raise ValueError("The independent term should be a sum of hypergeometric functions, got '%s'" % i_part)
- for coeff in h_part.values():
- 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.items():
- 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.items():
- H_part[k + K] = coeff.subs(n, n + K).expand()
- else:
- H_part = h_part
- K_max = max(H_part.keys())
- coeffs = [H_part[i] for i in range(K_max + 1)]
- result = rsolve_hyper(coeffs, -i_part, n, symbols=True)
- if result is None:
- return None
- solution, symbols = result
- if init in ({}, []):
- init = None
- if symbols and init is not None:
- if isinstance(init, list):
- init = {i: init[i] for i in range(len(init))}
- equations = []
- for k, v in init.items():
- 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
- if eq.has(S.NaN):
- eq = solution.limit(n, i) - v
- equations.append(eq)
- result = solve(equations, *symbols)
- if not result:
- return None
- else:
- solution = solution.subs(result)
- return solution
|