Spaces:
Sleeping
Sleeping
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.concrete import product | |
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 a 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 | |
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 | |
_C = C[:] | |
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 C != _C: | |
# renumber so they are contiguous | |
result = result.xreplace(dict(zip(C, _C))) | |
C = _C[:len(C)] | |
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. | |
The 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 the q-difference equations case. | |
Besides finding rational solutions alone, this functions is | |
an important part of Hyper algorithm where it is used to find | |
a particular solution for the 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) | |
C0*(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. | |
""" | |
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 = [g + h for g, h in similar.items()] | |
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:])) | |
# FIXME: The call to rsolve_ratio below should suffice (rsolve_poly | |
# call can be removed) but the XFAIL test_rsolve_ratio_missed must | |
# be fixed first. | |
R = rsolve_ratio(polys, Mul(*denoms), n, symbols=True) | |
if R is not None: | |
R, syms = R | |
if syms: | |
R = R.subs(zip(syms, [0]*len(syms))) | |
else: | |
R = rsolve_poly(polys, Mul(*denoms), n) | |
if R: | |
inhomogeneous[i] *= R | |
else: | |
return None | |
result = Add(*inhomogeneous) | |
result = simplify(result) | |
else: | |
result = S.Zero | |
Z = Dummy('Z') | |
p, q = coeffs[0], coeffs[r].subs(n, n - r + 1) | |
p_factors = list(roots(p, n).keys()) | |
q_factors = list(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. | |
sol = [Symbol("C" + str(len(symbols)))] | |
else: | |
sol, syms = rsolve_poly(recurr_coeffs, 0, n, len(symbols), symbols=True) | |
sol = sol.collect(syms) | |
sol = [sol.coeff(s) for s in syms] | |
for C in sol: | |
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)) | |
for C, ker in sk: | |
result += C * ker | |
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((x.is_hypergeometric(n) for x in 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 | |