recurr.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. r"""
  2. This module is intended for solving recurrences or, in other words,
  3. difference equations. Currently supported are linear, inhomogeneous
  4. equations with polynomial or rational coefficients.
  5. The solutions are obtained among polynomials, rational functions,
  6. hypergeometric terms, or combinations of hypergeometric term which
  7. are pairwise dissimilar.
  8. ``rsolve_X`` functions were meant as a low level interface
  9. for ``rsolve`` which would use Mathematica's syntax.
  10. Given a recurrence relation:
  11. .. math:: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) +
  12. ... + a_{0}(n) y(n) = f(n)
  13. where `k > 0` and `a_{i}(n)` are polynomials in `n`. To use
  14. ``rsolve_X`` we need to put all coefficients in to a list ``L`` of
  15. `k+1` elements the following way:
  16. ``L = [a_{0}(n), ..., a_{k-1}(n), a_{k}(n)]``
  17. where ``L[i]``, for `i=0, \ldots, k`, maps to
  18. `a_{i}(n) y(n+i)` (`y(n+i)` is implicit).
  19. For example if we would like to compute `m`-th Bernoulli polynomial
  20. up to a constant (example was taken from rsolve_poly docstring),
  21. then we would use `b(n+1) - b(n) = m n^{m-1}` recurrence, which
  22. has solution `b(n) = B_m + C`.
  23. Then ``L = [-1, 1]`` and `f(n) = m n^(m-1)` and finally for `m=4`:
  24. >>> from sympy import Symbol, bernoulli, rsolve_poly
  25. >>> n = Symbol('n', integer=True)
  26. >>> rsolve_poly([-1, 1], 4*n**3, n)
  27. C0 + n**4 - 2*n**3 + n**2
  28. >>> bernoulli(4, n)
  29. n**4 - 2*n**3 + n**2 - 1/30
  30. For the sake of completeness, `f(n)` can be:
  31. [1] a polynomial -> rsolve_poly
  32. [2] a rational function -> rsolve_ratio
  33. [3] a hypergeometric function -> rsolve_hyper
  34. """
  35. from collections import defaultdict
  36. from sympy.concrete import product
  37. from sympy.core.singleton import S
  38. from sympy.core.numbers import Rational, I
  39. from sympy.core.symbol import Symbol, Wild, Dummy
  40. from sympy.core.relational import Equality
  41. from sympy.core.add import Add
  42. from sympy.core.mul import Mul
  43. from sympy.core.sorting import default_sort_key
  44. from sympy.core.sympify import sympify
  45. from sympy.simplify import simplify, hypersimp, hypersimilar # type: ignore
  46. from sympy.solvers import solve, solve_undetermined_coeffs
  47. from sympy.polys import Poly, quo, gcd, lcm, roots, resultant
  48. from sympy.functions import binomial, factorial, FallingFactorial, RisingFactorial
  49. from sympy.matrices import Matrix, casoratian
  50. from sympy.utilities.iterables import numbered_symbols
  51. def rsolve_poly(coeffs, f, n, shift=0, **hints):
  52. r"""
  53. Given linear recurrence operator `\operatorname{L}` of order
  54. `k` with polynomial coefficients and inhomogeneous equation
  55. `\operatorname{L} y = f`, where `f` is a polynomial, we seek for
  56. all polynomial solutions over field `K` of characteristic zero.
  57. The algorithm performs two basic steps:
  58. (1) Compute degree `N` of the general polynomial solution.
  59. (2) Find all polynomials of degree `N` or less
  60. of `\operatorname{L} y = f`.
  61. There are two methods for computing the polynomial solutions.
  62. If the degree bound is relatively small, i.e. it's smaller than
  63. or equal to the order of the recurrence, then naive method of
  64. undetermined coefficients is being used. This gives a system
  65. of algebraic equations with `N+1` unknowns.
  66. In the other case, the algorithm performs transformation of the
  67. initial equation to an equivalent one for which the system of
  68. algebraic equations has only `r` indeterminates. This method is
  69. quite sophisticated (in comparison with the naive one) and was
  70. invented together by Abramov, Bronstein and Petkovsek.
  71. It is possible to generalize the algorithm implemented here to
  72. the case of linear q-difference and differential equations.
  73. Lets say that we would like to compute `m`-th Bernoulli polynomial
  74. up to a constant. For this we can use `b(n+1) - b(n) = m n^{m-1}`
  75. recurrence, which has solution `b(n) = B_m + C`. For example:
  76. >>> from sympy import Symbol, rsolve_poly
  77. >>> n = Symbol('n', integer=True)
  78. >>> rsolve_poly([-1, 1], 4*n**3, n)
  79. C0 + n**4 - 2*n**3 + n**2
  80. References
  81. ==========
  82. .. [1] S. A. Abramov, M. Bronstein and M. Petkovsek, On polynomial
  83. solutions of linear operator equations, in: T. Levelt, ed.,
  84. Proc. ISSAC '95, ACM Press, New York, 1995, 290-296.
  85. .. [2] M. Petkovsek, Hypergeometric solutions of linear recurrences
  86. with polynomial coefficients, J. Symbolic Computation,
  87. 14 (1992), 243-264.
  88. .. [3] M. Petkovsek, H. S. Wilf, D. Zeilberger, A = B, 1996.
  89. """
  90. f = sympify(f)
  91. if not f.is_polynomial(n):
  92. return None
  93. homogeneous = f.is_zero
  94. r = len(coeffs) - 1
  95. coeffs = [Poly(coeff, n) for coeff in coeffs]
  96. polys = [Poly(0, n)]*(r + 1)
  97. terms = [(S.Zero, S.NegativeInfinity)]*(r + 1)
  98. for i in range(r + 1):
  99. for j in range(i, r + 1):
  100. polys[i] += coeffs[j]*(binomial(j, i).as_poly(n))
  101. if not polys[i].is_zero:
  102. (exp,), coeff = polys[i].LT()
  103. terms[i] = (coeff, exp)
  104. d = b = terms[0][1]
  105. for i in range(1, r + 1):
  106. if terms[i][1] > d:
  107. d = terms[i][1]
  108. if terms[i][1] - i > b:
  109. b = terms[i][1] - i
  110. d, b = int(d), int(b)
  111. x = Dummy('x')
  112. degree_poly = S.Zero
  113. for i in range(r + 1):
  114. if terms[i][1] - i == b:
  115. degree_poly += terms[i][0]*FallingFactorial(x, i)
  116. nni_roots = list(roots(degree_poly, x, filter='Z',
  117. predicate=lambda r: r >= 0).keys())
  118. if nni_roots:
  119. N = [max(nni_roots)]
  120. else:
  121. N = []
  122. if homogeneous:
  123. N += [-b - 1]
  124. else:
  125. N += [f.as_poly(n).degree() - b, -b - 1]
  126. N = int(max(N))
  127. if N < 0:
  128. if homogeneous:
  129. if hints.get('symbols', False):
  130. return (S.Zero, [])
  131. else:
  132. return S.Zero
  133. else:
  134. return None
  135. if N <= r:
  136. C = []
  137. y = E = S.Zero
  138. for i in range(N + 1):
  139. C.append(Symbol('C' + str(i + shift)))
  140. y += C[i] * n**i
  141. for i in range(r + 1):
  142. E += coeffs[i].as_expr()*y.subs(n, n + i)
  143. solutions = solve_undetermined_coeffs(E - f, C, n)
  144. if solutions is not None:
  145. _C = C
  146. C = [c for c in C if (c not in solutions)]
  147. result = y.subs(solutions)
  148. else:
  149. return None # TBD
  150. else:
  151. A = r
  152. U = N + A + b + 1
  153. nni_roots = list(roots(polys[r], filter='Z',
  154. predicate=lambda r: r >= 0).keys())
  155. if nni_roots != []:
  156. a = max(nni_roots) + 1
  157. else:
  158. a = S.Zero
  159. def _zero_vector(k):
  160. return [S.Zero] * k
  161. def _one_vector(k):
  162. return [S.One] * k
  163. def _delta(p, k):
  164. B = S.One
  165. D = p.subs(n, a + k)
  166. for i in range(1, k + 1):
  167. B *= Rational(i - k - 1, i)
  168. D += B * p.subs(n, a + k - i)
  169. return D
  170. alpha = {}
  171. for i in range(-A, d + 1):
  172. I = _one_vector(d + 1)
  173. for k in range(1, d + 1):
  174. I[k] = I[k - 1] * (x + i - k + 1)/k
  175. alpha[i] = S.Zero
  176. for j in range(A + 1):
  177. for k in range(d + 1):
  178. B = binomial(k, i + j)
  179. D = _delta(polys[j].as_expr(), k)
  180. alpha[i] += I[k]*B*D
  181. V = Matrix(U, A, lambda i, j: int(i == j))
  182. if homogeneous:
  183. for i in range(A, U):
  184. v = _zero_vector(A)
  185. for k in range(1, A + b + 1):
  186. if i - k < 0:
  187. break
  188. B = alpha[k - A].subs(x, i - k)
  189. for j in range(A):
  190. v[j] += B * V[i - k, j]
  191. denom = alpha[-A].subs(x, i)
  192. for j in range(A):
  193. V[i, j] = -v[j] / denom
  194. else:
  195. G = _zero_vector(U)
  196. for i in range(A, U):
  197. v = _zero_vector(A)
  198. g = S.Zero
  199. for k in range(1, A + b + 1):
  200. if i - k < 0:
  201. break
  202. B = alpha[k - A].subs(x, i - k)
  203. for j in range(A):
  204. v[j] += B * V[i - k, j]
  205. g += B * G[i - k]
  206. denom = alpha[-A].subs(x, i)
  207. for j in range(A):
  208. V[i, j] = -v[j] / denom
  209. G[i] = (_delta(f, i - A) - g) / denom
  210. P, Q = _one_vector(U), _zero_vector(A)
  211. for i in range(1, U):
  212. P[i] = (P[i - 1] * (n - a - i + 1)/i).expand()
  213. for i in range(A):
  214. Q[i] = Add(*[(v*p).expand() for v, p in zip(V[:, i], P)])
  215. if not homogeneous:
  216. h = Add(*[(g*p).expand() for g, p in zip(G, P)])
  217. C = [Symbol('C' + str(i + shift)) for i in range(A)]
  218. g = lambda i: Add(*[c*_delta(q, i) for c, q in zip(C, Q)])
  219. if homogeneous:
  220. E = [g(i) for i in range(N + 1, U)]
  221. else:
  222. E = [g(i) + _delta(h, i) for i in range(N + 1, U)]
  223. if E != []:
  224. solutions = solve(E, *C)
  225. if not solutions:
  226. if homogeneous:
  227. if hints.get('symbols', False):
  228. return (S.Zero, [])
  229. else:
  230. return S.Zero
  231. else:
  232. return None
  233. else:
  234. solutions = {}
  235. if homogeneous:
  236. result = S.Zero
  237. else:
  238. result = h
  239. _C = C[:]
  240. for c, q in list(zip(C, Q)):
  241. if c in solutions:
  242. s = solutions[c]*q
  243. C.remove(c)
  244. else:
  245. s = c*q
  246. result += s.expand()
  247. if C != _C:
  248. # renumber so they are contiguous
  249. result = result.xreplace(dict(zip(C, _C)))
  250. C = _C[:len(C)]
  251. if hints.get('symbols', False):
  252. return (result, C)
  253. else:
  254. return result
  255. def rsolve_ratio(coeffs, f, n, **hints):
  256. r"""
  257. Given linear recurrence operator `\operatorname{L}` of order `k`
  258. with polynomial coefficients and inhomogeneous equation
  259. `\operatorname{L} y = f`, where `f` is a polynomial, we seek
  260. for all rational solutions over field `K` of characteristic zero.
  261. This procedure accepts only polynomials, however if you are
  262. interested in solving recurrence with rational coefficients
  263. then use ``rsolve`` which will pre-process the given equation
  264. and run this procedure with polynomial arguments.
  265. The algorithm performs two basic steps:
  266. (1) Compute polynomial `v(n)` which can be used as universal
  267. denominator of any rational solution of equation
  268. `\operatorname{L} y = f`.
  269. (2) Construct new linear difference equation by substitution
  270. `y(n) = u(n)/v(n)` and solve it for `u(n)` finding all its
  271. polynomial solutions. Return ``None`` if none were found.
  272. The algorithm implemented here is a revised version of the original
  273. Abramov's algorithm, developed in 1989. The new approach is much
  274. simpler to implement and has better overall efficiency. This
  275. method can be easily adapted to the q-difference equations case.
  276. Besides finding rational solutions alone, this functions is
  277. an important part of Hyper algorithm where it is used to find
  278. a particular solution for the inhomogeneous part of a recurrence.
  279. Examples
  280. ========
  281. >>> from sympy.abc import x
  282. >>> from sympy.solvers.recurr import rsolve_ratio
  283. >>> rsolve_ratio([-2*x**3 + x**2 + 2*x - 1, 2*x**3 + x**2 - 6*x,
  284. ... - 2*x**3 - 11*x**2 - 18*x - 9, 2*x**3 + 13*x**2 + 22*x + 8], 0, x)
  285. C0*(2*x - 3)/(2*(x**2 - 1))
  286. References
  287. ==========
  288. .. [1] S. A. Abramov, Rational solutions of linear difference
  289. and q-difference equations with polynomial coefficients,
  290. in: T. Levelt, ed., Proc. ISSAC '95, ACM Press, New York,
  291. 1995, 285-289
  292. See Also
  293. ========
  294. rsolve_hyper
  295. """
  296. f = sympify(f)
  297. if not f.is_polynomial(n):
  298. return None
  299. coeffs = list(map(sympify, coeffs))
  300. r = len(coeffs) - 1
  301. A, B = coeffs[r], coeffs[0]
  302. A = A.subs(n, n - r).expand()
  303. h = Dummy('h')
  304. res = resultant(A, B.subs(n, n + h), n)
  305. if not res.is_polynomial(h):
  306. p, q = res.as_numer_denom()
  307. res = quo(p, q, h)
  308. nni_roots = list(roots(res, h, filter='Z',
  309. predicate=lambda r: r >= 0).keys())
  310. if not nni_roots:
  311. return rsolve_poly(coeffs, f, n, **hints)
  312. else:
  313. C, numers = S.One, [S.Zero]*(r + 1)
  314. for i in range(int(max(nni_roots)), -1, -1):
  315. d = gcd(A, B.subs(n, n + i), n)
  316. A = quo(A, d, n)
  317. B = quo(B, d.subs(n, n - i), n)
  318. C *= Mul(*[d.subs(n, n - j) for j in range(i + 1)])
  319. denoms = [C.subs(n, n + i) for i in range(r + 1)]
  320. for i in range(r + 1):
  321. g = gcd(coeffs[i], denoms[i], n)
  322. numers[i] = quo(coeffs[i], g, n)
  323. denoms[i] = quo(denoms[i], g, n)
  324. for i in range(r + 1):
  325. numers[i] *= Mul(*(denoms[:i] + denoms[i + 1:]))
  326. result = rsolve_poly(numers, f * Mul(*denoms), n, **hints)
  327. if result is not None:
  328. if hints.get('symbols', False):
  329. return (simplify(result[0] / C), result[1])
  330. else:
  331. return simplify(result / C)
  332. else:
  333. return None
  334. def rsolve_hyper(coeffs, f, n, **hints):
  335. r"""
  336. Given linear recurrence operator `\operatorname{L}` of order `k`
  337. with polynomial coefficients and inhomogeneous equation
  338. `\operatorname{L} y = f` we seek for all hypergeometric solutions
  339. over field `K` of characteristic zero.
  340. The inhomogeneous part can be either hypergeometric or a sum
  341. of a fixed number of pairwise dissimilar hypergeometric terms.
  342. The algorithm performs three basic steps:
  343. (1) Group together similar hypergeometric terms in the
  344. inhomogeneous part of `\operatorname{L} y = f`, and find
  345. particular solution using Abramov's algorithm.
  346. (2) Compute generating set of `\operatorname{L}` and find basis
  347. in it, so that all solutions are linearly independent.
  348. (3) Form final solution with the number of arbitrary
  349. constants equal to dimension of basis of `\operatorname{L}`.
  350. Term `a(n)` is hypergeometric if it is annihilated by first order
  351. linear difference equations with polynomial coefficients or, in
  352. simpler words, if consecutive term ratio is a rational function.
  353. The output of this procedure is a linear combination of fixed
  354. number of hypergeometric terms. However the underlying method
  355. can generate larger class of solutions - D'Alembertian terms.
  356. Note also that this method not only computes the kernel of the
  357. inhomogeneous equation, but also reduces in to a basis so that
  358. solutions generated by this procedure are linearly independent
  359. Examples
  360. ========
  361. >>> from sympy.solvers import rsolve_hyper
  362. >>> from sympy.abc import x
  363. >>> rsolve_hyper([-1, -1, 1], 0, x)
  364. C0*(1/2 - sqrt(5)/2)**x + C1*(1/2 + sqrt(5)/2)**x
  365. >>> rsolve_hyper([-1, 1], 1 + x, x)
  366. C0 + x*(x + 1)/2
  367. References
  368. ==========
  369. .. [1] M. Petkovsek, Hypergeometric solutions of linear recurrences
  370. with polynomial coefficients, J. Symbolic Computation,
  371. 14 (1992), 243-264.
  372. .. [2] M. Petkovsek, H. S. Wilf, D. Zeilberger, A = B, 1996.
  373. """
  374. coeffs = list(map(sympify, coeffs))
  375. f = sympify(f)
  376. r, kernel, symbols = len(coeffs) - 1, [], set()
  377. if not f.is_zero:
  378. if f.is_Add:
  379. similar = {}
  380. for g in f.expand().args:
  381. if not g.is_hypergeometric(n):
  382. return None
  383. for h in similar.keys():
  384. if hypersimilar(g, h, n):
  385. similar[h] += g
  386. break
  387. else:
  388. similar[g] = S.Zero
  389. inhomogeneous = [g + h for g, h in similar.items()]
  390. elif f.is_hypergeometric(n):
  391. inhomogeneous = [f]
  392. else:
  393. return None
  394. for i, g in enumerate(inhomogeneous):
  395. coeff, polys = S.One, coeffs[:]
  396. denoms = [S.One]*(r + 1)
  397. s = hypersimp(g, n)
  398. for j in range(1, r + 1):
  399. coeff *= s.subs(n, n + j - 1)
  400. p, q = coeff.as_numer_denom()
  401. polys[j] *= p
  402. denoms[j] = q
  403. for j in range(r + 1):
  404. polys[j] *= Mul(*(denoms[:j] + denoms[j + 1:]))
  405. # FIXME: The call to rsolve_ratio below should suffice (rsolve_poly
  406. # call can be removed) but the XFAIL test_rsolve_ratio_missed must
  407. # be fixed first.
  408. R = rsolve_ratio(polys, Mul(*denoms), n, symbols=True)
  409. if R is not None:
  410. R, syms = R
  411. if syms:
  412. R = R.subs(zip(syms, [0]*len(syms)))
  413. else:
  414. R = rsolve_poly(polys, Mul(*denoms), n)
  415. if R:
  416. inhomogeneous[i] *= R
  417. else:
  418. return None
  419. result = Add(*inhomogeneous)
  420. result = simplify(result)
  421. else:
  422. result = S.Zero
  423. Z = Dummy('Z')
  424. p, q = coeffs[0], coeffs[r].subs(n, n - r + 1)
  425. p_factors = list(roots(p, n).keys())
  426. q_factors = list(roots(q, n).keys())
  427. factors = [(S.One, S.One)]
  428. for p in p_factors:
  429. for q in q_factors:
  430. if p.is_integer and q.is_integer and p <= q:
  431. continue
  432. else:
  433. factors += [(n - p, n - q)]
  434. p = [(n - p, S.One) for p in p_factors]
  435. q = [(S.One, n - q) for q in q_factors]
  436. factors = p + factors + q
  437. for A, B in factors:
  438. polys, degrees = [], []
  439. D = A*B.subs(n, n + r - 1)
  440. for i in range(r + 1):
  441. a = Mul(*[A.subs(n, n + j) for j in range(i)])
  442. b = Mul(*[B.subs(n, n + j) for j in range(i, r)])
  443. poly = quo(coeffs[i]*a*b, D, n)
  444. polys.append(poly.as_poly(n))
  445. if not poly.is_zero:
  446. degrees.append(polys[i].degree())
  447. if degrees:
  448. d, poly = max(degrees), S.Zero
  449. else:
  450. return None
  451. for i in range(r + 1):
  452. coeff = polys[i].nth(d)
  453. if coeff is not S.Zero:
  454. poly += coeff * Z**i
  455. for z in roots(poly, Z).keys():
  456. if z.is_zero:
  457. continue
  458. recurr_coeffs = [polys[i].as_expr()*z**i for i in range(r + 1)]
  459. if d == 0 and 0 != Add(*[recurr_coeffs[j]*j for j in range(1, r + 1)]):
  460. # faster inline check (than calling rsolve_poly) for a
  461. # constant solution to a constant coefficient recurrence.
  462. sol = [Symbol("C" + str(len(symbols)))]
  463. else:
  464. sol, syms = rsolve_poly(recurr_coeffs, 0, n, len(symbols), symbols=True)
  465. sol = sol.collect(syms)
  466. sol = [sol.coeff(s) for s in syms]
  467. for C in sol:
  468. ratio = z * A * C.subs(n, n + 1) / B / C
  469. ratio = simplify(ratio)
  470. # If there is a nonnegative root in the denominator of the ratio,
  471. # this indicates that the term y(n_root) is zero, and one should
  472. # start the product with the term y(n_root + 1).
  473. n0 = 0
  474. for n_root in roots(ratio.as_numer_denom()[1], n).keys():
  475. if n_root.has(I):
  476. return None
  477. elif (n0 < (n_root + 1)) == True:
  478. n0 = n_root + 1
  479. K = product(ratio, (n, n0, n - 1))
  480. if K.has(factorial, FallingFactorial, RisingFactorial):
  481. K = simplify(K)
  482. if casoratian(kernel + [K], n, zero=False) != 0:
  483. kernel.append(K)
  484. kernel.sort(key=default_sort_key)
  485. sk = list(zip(numbered_symbols('C'), kernel))
  486. for C, ker in sk:
  487. result += C * ker
  488. if hints.get('symbols', False):
  489. # XXX: This returns the symbols in a non-deterministic order
  490. symbols |= {s for s, k in sk}
  491. return (result, list(symbols))
  492. else:
  493. return result
  494. def rsolve(f, y, init=None):
  495. r"""
  496. Solve univariate recurrence with rational coefficients.
  497. Given `k`-th order linear recurrence `\operatorname{L} y = f`,
  498. or equivalently:
  499. .. math:: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) +
  500. \cdots + a_{0}(n) y(n) = f(n)
  501. where `a_{i}(n)`, for `i=0, \ldots, k`, are polynomials or rational
  502. functions in `n`, and `f` is a hypergeometric function or a sum
  503. of a fixed number of pairwise dissimilar hypergeometric terms in
  504. `n`, finds all solutions or returns ``None``, if none were found.
  505. Initial conditions can be given as a dictionary in two forms:
  506. (1) ``{ n_0 : v_0, n_1 : v_1, ..., n_m : v_m}``
  507. (2) ``{y(n_0) : v_0, y(n_1) : v_1, ..., y(n_m) : v_m}``
  508. or as a list ``L`` of values:
  509. ``L = [v_0, v_1, ..., v_m]``
  510. where ``L[i] = v_i``, for `i=0, \ldots, m`, maps to `y(n_i)`.
  511. Examples
  512. ========
  513. Lets consider the following recurrence:
  514. .. math:: (n - 1) y(n + 2) - (n^2 + 3 n - 2) y(n + 1) +
  515. 2 n (n + 1) y(n) = 0
  516. >>> from sympy import Function, rsolve
  517. >>> from sympy.abc import n
  518. >>> y = Function('y')
  519. >>> f = (n - 1)*y(n + 2) - (n**2 + 3*n - 2)*y(n + 1) + 2*n*(n + 1)*y(n)
  520. >>> rsolve(f, y(n))
  521. 2**n*C0 + C1*factorial(n)
  522. >>> rsolve(f, y(n), {y(0):0, y(1):3})
  523. 3*2**n - 3*factorial(n)
  524. See Also
  525. ========
  526. rsolve_poly, rsolve_ratio, rsolve_hyper
  527. """
  528. if isinstance(f, Equality):
  529. f = f.lhs - f.rhs
  530. n = y.args[0]
  531. k = Wild('k', exclude=(n,))
  532. # Preprocess user input to allow things like
  533. # y(n) + a*(y(n + 1) + y(n - 1))/2
  534. f = f.expand().collect(y.func(Wild('m', integer=True)))
  535. h_part = defaultdict(list)
  536. i_part = []
  537. for g in Add.make_args(f):
  538. coeff, dep = g.as_coeff_mul(y.func)
  539. if not dep:
  540. i_part.append(coeff)
  541. continue
  542. for h in dep:
  543. if h.is_Function and h.func == y.func:
  544. result = h.args[0].match(n + k)
  545. if result is not None:
  546. h_part[int(result[k])].append(coeff)
  547. continue
  548. raise ValueError(
  549. "'%s(%s + k)' expected, got '%s'" % (y.func, n, h))
  550. for k in h_part:
  551. h_part[k] = Add(*h_part[k])
  552. h_part.default_factory = lambda: 0
  553. i_part = Add(*i_part)
  554. for k, coeff in h_part.items():
  555. h_part[k] = simplify(coeff)
  556. common = S.One
  557. if not i_part.is_zero and not i_part.is_hypergeometric(n) and \
  558. not (i_part.is_Add and all((x.is_hypergeometric(n) for x in i_part.expand().args))):
  559. raise ValueError("The independent term should be a sum of hypergeometric functions, got '%s'" % i_part)
  560. for coeff in h_part.values():
  561. if coeff.is_rational_function(n):
  562. if not coeff.is_polynomial(n):
  563. common = lcm(common, coeff.as_numer_denom()[1], n)
  564. else:
  565. raise ValueError(
  566. "Polynomial or rational function expected, got '%s'" % coeff)
  567. i_numer, i_denom = i_part.as_numer_denom()
  568. if i_denom.is_polynomial(n):
  569. common = lcm(common, i_denom, n)
  570. if common is not S.One:
  571. for k, coeff in h_part.items():
  572. numer, denom = coeff.as_numer_denom()
  573. h_part[k] = numer*quo(common, denom, n)
  574. i_part = i_numer*quo(common, i_denom, n)
  575. K_min = min(h_part.keys())
  576. if K_min < 0:
  577. K = abs(K_min)
  578. H_part = defaultdict(lambda: S.Zero)
  579. i_part = i_part.subs(n, n + K).expand()
  580. common = common.subs(n, n + K).expand()
  581. for k, coeff in h_part.items():
  582. H_part[k + K] = coeff.subs(n, n + K).expand()
  583. else:
  584. H_part = h_part
  585. K_max = max(H_part.keys())
  586. coeffs = [H_part[i] for i in range(K_max + 1)]
  587. result = rsolve_hyper(coeffs, -i_part, n, symbols=True)
  588. if result is None:
  589. return None
  590. solution, symbols = result
  591. if init in ({}, []):
  592. init = None
  593. if symbols and init is not None:
  594. if isinstance(init, list):
  595. init = {i: init[i] for i in range(len(init))}
  596. equations = []
  597. for k, v in init.items():
  598. try:
  599. i = int(k)
  600. except TypeError:
  601. if k.is_Function and k.func == y.func:
  602. i = int(k.args[0])
  603. else:
  604. raise ValueError("Integer or term expected, got '%s'" % k)
  605. eq = solution.subs(n, i) - v
  606. if eq.has(S.NaN):
  607. eq = solution.limit(n, i) - v
  608. equations.append(eq)
  609. result = solve(equations, *symbols)
  610. if not result:
  611. return None
  612. else:
  613. solution = solution.subs(result)
  614. return solution