gosper.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. """Gosper's algorithm for hypergeometric summation. """
  2. from sympy.core import S, Dummy, symbols
  3. from sympy.polys import Poly, parallel_poly_from_expr, factor
  4. from sympy.utilities.iterables import is_sequence
  5. def gosper_normal(f, g, n, polys=True):
  6. r"""
  7. Compute the Gosper's normal form of ``f`` and ``g``.
  8. Explanation
  9. ===========
  10. Given relatively prime univariate polynomials ``f`` and ``g``,
  11. rewrite their quotient to a normal form defined as follows:
  12. .. math::
  13. \frac{f(n)}{g(n)} = Z \cdot \frac{A(n) C(n+1)}{B(n) C(n)}
  14. where ``Z`` is an arbitrary constant and ``A``, ``B``, ``C`` are
  15. monic polynomials in ``n`` with the following properties:
  16. 1. `\gcd(A(n), B(n+h)) = 1 \forall h \in \mathbb{N}`
  17. 2. `\gcd(B(n), C(n+1)) = 1`
  18. 3. `\gcd(A(n), C(n)) = 1`
  19. This normal form, or rational factorization in other words, is a
  20. crucial step in Gosper's algorithm and in solving of difference
  21. equations. It can be also used to decide if two hypergeometric
  22. terms are similar or not.
  23. This procedure will return a tuple containing elements of this
  24. factorization in the form ``(Z*A, B, C)``.
  25. Examples
  26. ========
  27. >>> from sympy.concrete.gosper import gosper_normal
  28. >>> from sympy.abc import n
  29. >>> gosper_normal(4*n+5, 2*(4*n+1)*(2*n+3), n, polys=False)
  30. (1/4, n + 3/2, n + 1/4)
  31. """
  32. (p, q), opt = parallel_poly_from_expr(
  33. (f, g), n, field=True, extension=True)
  34. a, A = p.LC(), p.monic()
  35. b, B = q.LC(), q.monic()
  36. C, Z = A.one, a/b
  37. h = Dummy('h')
  38. D = Poly(n + h, n, h, domain=opt.domain)
  39. R = A.resultant(B.compose(D))
  40. roots = set(R.ground_roots().keys())
  41. for r in set(roots):
  42. if not r.is_Integer or r < 0:
  43. roots.remove(r)
  44. for i in sorted(roots):
  45. d = A.gcd(B.shift(+i))
  46. A = A.quo(d)
  47. B = B.quo(d.shift(-i))
  48. for j in range(1, i + 1):
  49. C *= d.shift(-j)
  50. A = A.mul_ground(Z)
  51. if not polys:
  52. A = A.as_expr()
  53. B = B.as_expr()
  54. C = C.as_expr()
  55. return A, B, C
  56. def gosper_term(f, n):
  57. r"""
  58. Compute Gosper's hypergeometric term for ``f``.
  59. Explanation
  60. ===========
  61. Suppose ``f`` is a hypergeometric term such that:
  62. .. math::
  63. s_n = \sum_{k=0}^{n-1} f_k
  64. and `f_k` does not depend on `n`. Returns a hypergeometric
  65. term `g_n` such that `g_{n+1} - g_n = f_n`.
  66. Examples
  67. ========
  68. >>> from sympy.concrete.gosper import gosper_term
  69. >>> from sympy import factorial
  70. >>> from sympy.abc import n
  71. >>> gosper_term((4*n + 1)*factorial(n)/factorial(2*n + 1), n)
  72. (-n - 1/2)/(n + 1/4)
  73. """
  74. from sympy.simplify import hypersimp
  75. r = hypersimp(f, n)
  76. if r is None:
  77. return None # 'f' is *not* a hypergeometric term
  78. p, q = r.as_numer_denom()
  79. A, B, C = gosper_normal(p, q, n)
  80. B = B.shift(-1)
  81. N = S(A.degree())
  82. M = S(B.degree())
  83. K = S(C.degree())
  84. if (N != M) or (A.LC() != B.LC()):
  85. D = {K - max(N, M)}
  86. elif not N:
  87. D = {K - N + 1, S.Zero}
  88. else:
  89. D = {K - N + 1, (B.nth(N - 1) - A.nth(N - 1))/A.LC()}
  90. for d in set(D):
  91. if not d.is_Integer or d < 0:
  92. D.remove(d)
  93. if not D:
  94. return None # 'f(n)' is *not* Gosper-summable
  95. d = max(D)
  96. coeffs = symbols('c:%s' % (d + 1), cls=Dummy)
  97. domain = A.get_domain().inject(*coeffs)
  98. x = Poly(coeffs, n, domain=domain)
  99. H = A*x.shift(1) - B*x - C
  100. from sympy.solvers.solvers import solve
  101. solution = solve(H.coeffs(), coeffs)
  102. if solution is None:
  103. return None # 'f(n)' is *not* Gosper-summable
  104. x = x.as_expr().subs(solution)
  105. for coeff in coeffs:
  106. if coeff not in solution:
  107. x = x.subs(coeff, 0)
  108. if x.is_zero:
  109. return None # 'f(n)' is *not* Gosper-summable
  110. else:
  111. return B.as_expr()*x/C.as_expr()
  112. def gosper_sum(f, k):
  113. r"""
  114. Gosper's hypergeometric summation algorithm.
  115. Explanation
  116. ===========
  117. Given a hypergeometric term ``f`` such that:
  118. .. math ::
  119. s_n = \sum_{k=0}^{n-1} f_k
  120. and `f(n)` does not depend on `n`, returns `g_{n} - g(0)` where
  121. `g_{n+1} - g_n = f_n`, or ``None`` if `s_n` cannot be expressed
  122. in closed form as a sum of hypergeometric terms.
  123. Examples
  124. ========
  125. >>> from sympy.concrete.gosper import gosper_sum
  126. >>> from sympy import factorial
  127. >>> from sympy.abc import n, k
  128. >>> f = (4*k + 1)*factorial(k)/factorial(2*k + 1)
  129. >>> gosper_sum(f, (k, 0, n))
  130. (-factorial(n) + 2*factorial(2*n + 1))/factorial(2*n + 1)
  131. >>> _.subs(n, 2) == sum(f.subs(k, i) for i in [0, 1, 2])
  132. True
  133. >>> gosper_sum(f, (k, 3, n))
  134. (-60*factorial(n) + factorial(2*n + 1))/(60*factorial(2*n + 1))
  135. >>> _.subs(n, 5) == sum(f.subs(k, i) for i in [3, 4, 5])
  136. True
  137. References
  138. ==========
  139. .. [1] Marko Petkovsek, Herbert S. Wilf, Doron Zeilberger, A = B,
  140. AK Peters, Ltd., Wellesley, MA, USA, 1997, pp. 73--100
  141. """
  142. indefinite = False
  143. if is_sequence(k):
  144. k, a, b = k
  145. else:
  146. indefinite = True
  147. g = gosper_term(f, k)
  148. if g is None:
  149. return None
  150. if indefinite:
  151. result = f*g
  152. else:
  153. result = (f*(g + 1)).subs(k, b) - (f*g).subs(k, a)
  154. if result is S.NaN:
  155. try:
  156. result = (f*(g + 1)).limit(k, b) - (f*g).limit(k, a)
  157. except NotImplementedError:
  158. result = None
  159. return factor(result)