Coverage for /usr/lib/python3/dist-packages/sympy/concrete/gosper.py: 7%
81 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Gosper's algorithm for hypergeometric summation. """
3from sympy.core import S, Dummy, symbols
4from sympy.polys import Poly, parallel_poly_from_expr, factor
5from sympy.utilities.iterables import is_sequence
8def gosper_normal(f, g, n, polys=True):
9 r"""
10 Compute the Gosper's normal form of ``f`` and ``g``.
12 Explanation
13 ===========
15 Given relatively prime univariate polynomials ``f`` and ``g``,
16 rewrite their quotient to a normal form defined as follows:
18 .. math::
19 \frac{f(n)}{g(n)} = Z \cdot \frac{A(n) C(n+1)}{B(n) C(n)}
21 where ``Z`` is an arbitrary constant and ``A``, ``B``, ``C`` are
22 monic polynomials in ``n`` with the following properties:
24 1. `\gcd(A(n), B(n+h)) = 1 \forall h \in \mathbb{N}`
25 2. `\gcd(B(n), C(n+1)) = 1`
26 3. `\gcd(A(n), C(n)) = 1`
28 This normal form, or rational factorization in other words, is a
29 crucial step in Gosper's algorithm and in solving of difference
30 equations. It can be also used to decide if two hypergeometric
31 terms are similar or not.
33 This procedure will return a tuple containing elements of this
34 factorization in the form ``(Z*A, B, C)``.
36 Examples
37 ========
39 >>> from sympy.concrete.gosper import gosper_normal
40 >>> from sympy.abc import n
42 >>> gosper_normal(4*n+5, 2*(4*n+1)*(2*n+3), n, polys=False)
43 (1/4, n + 3/2, n + 1/4)
45 """
46 (p, q), opt = parallel_poly_from_expr(
47 (f, g), n, field=True, extension=True)
49 a, A = p.LC(), p.monic()
50 b, B = q.LC(), q.monic()
52 C, Z = A.one, a/b
53 h = Dummy('h')
55 D = Poly(n + h, n, h, domain=opt.domain)
57 R = A.resultant(B.compose(D))
58 roots = set(R.ground_roots().keys())
60 for r in set(roots):
61 if not r.is_Integer or r < 0:
62 roots.remove(r)
64 for i in sorted(roots):
65 d = A.gcd(B.shift(+i))
67 A = A.quo(d)
68 B = B.quo(d.shift(-i))
70 for j in range(1, i + 1):
71 C *= d.shift(-j)
73 A = A.mul_ground(Z)
75 if not polys:
76 A = A.as_expr()
77 B = B.as_expr()
78 C = C.as_expr()
80 return A, B, C
83def gosper_term(f, n):
84 r"""
85 Compute Gosper's hypergeometric term for ``f``.
87 Explanation
88 ===========
90 Suppose ``f`` is a hypergeometric term such that:
92 .. math::
93 s_n = \sum_{k=0}^{n-1} f_k
95 and `f_k` does not depend on `n`. Returns a hypergeometric
96 term `g_n` such that `g_{n+1} - g_n = f_n`.
98 Examples
99 ========
101 >>> from sympy.concrete.gosper import gosper_term
102 >>> from sympy import factorial
103 >>> from sympy.abc import n
105 >>> gosper_term((4*n + 1)*factorial(n)/factorial(2*n + 1), n)
106 (-n - 1/2)/(n + 1/4)
108 """
109 from sympy.simplify import hypersimp
110 r = hypersimp(f, n)
112 if r is None:
113 return None # 'f' is *not* a hypergeometric term
115 p, q = r.as_numer_denom()
117 A, B, C = gosper_normal(p, q, n)
118 B = B.shift(-1)
120 N = S(A.degree())
121 M = S(B.degree())
122 K = S(C.degree())
124 if (N != M) or (A.LC() != B.LC()):
125 D = {K - max(N, M)}
126 elif not N:
127 D = {K - N + 1, S.Zero}
128 else:
129 D = {K - N + 1, (B.nth(N - 1) - A.nth(N - 1))/A.LC()}
131 for d in set(D):
132 if not d.is_Integer or d < 0:
133 D.remove(d)
135 if not D:
136 return None # 'f(n)' is *not* Gosper-summable
138 d = max(D)
140 coeffs = symbols('c:%s' % (d + 1), cls=Dummy)
141 domain = A.get_domain().inject(*coeffs)
143 x = Poly(coeffs, n, domain=domain)
144 H = A*x.shift(1) - B*x - C
146 from sympy.solvers.solvers import solve
147 solution = solve(H.coeffs(), coeffs)
149 if solution is None:
150 return None # 'f(n)' is *not* Gosper-summable
152 x = x.as_expr().subs(solution)
154 for coeff in coeffs:
155 if coeff not in solution:
156 x = x.subs(coeff, 0)
158 if x.is_zero:
159 return None # 'f(n)' is *not* Gosper-summable
160 else:
161 return B.as_expr()*x/C.as_expr()
164def gosper_sum(f, k):
165 r"""
166 Gosper's hypergeometric summation algorithm.
168 Explanation
169 ===========
171 Given a hypergeometric term ``f`` such that:
173 .. math ::
174 s_n = \sum_{k=0}^{n-1} f_k
176 and `f(n)` does not depend on `n`, returns `g_{n} - g(0)` where
177 `g_{n+1} - g_n = f_n`, or ``None`` if `s_n` cannot be expressed
178 in closed form as a sum of hypergeometric terms.
180 Examples
181 ========
183 >>> from sympy.concrete.gosper import gosper_sum
184 >>> from sympy import factorial
185 >>> from sympy.abc import n, k
187 >>> f = (4*k + 1)*factorial(k)/factorial(2*k + 1)
188 >>> gosper_sum(f, (k, 0, n))
189 (-factorial(n) + 2*factorial(2*n + 1))/factorial(2*n + 1)
190 >>> _.subs(n, 2) == sum(f.subs(k, i) for i in [0, 1, 2])
191 True
192 >>> gosper_sum(f, (k, 3, n))
193 (-60*factorial(n) + factorial(2*n + 1))/(60*factorial(2*n + 1))
194 >>> _.subs(n, 5) == sum(f.subs(k, i) for i in [3, 4, 5])
195 True
197 References
198 ==========
200 .. [1] Marko Petkovsek, Herbert S. Wilf, Doron Zeilberger, A = B,
201 AK Peters, Ltd., Wellesley, MA, USA, 1997, pp. 73--100
203 """
204 indefinite = False
206 if is_sequence(k):
207 k, a, b = k
208 else:
209 indefinite = True
211 g = gosper_term(f, k)
213 if g is None:
214 return None
216 if indefinite:
217 result = f*g
218 else:
219 result = (f*(g + 1)).subs(k, b) - (f*g).subs(k, a)
221 if result is S.NaN:
222 try:
223 result = (f*(g + 1)).limit(k, b) - (f*g).limit(k, a)
224 except NotImplementedError:
225 result = None
227 return factor(result)