Coverage for /usr/lib/python3/dist-packages/sympy/series/limitseq.py: 16%
120 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"""Limits of sequences"""
3from sympy.calculus.accumulationbounds import AccumulationBounds
4from sympy.core.add import Add
5from sympy.core.function import PoleError
6from sympy.core.power import Pow
7from sympy.core.singleton import S
8from sympy.core.symbol import Dummy
9from sympy.core.sympify import sympify
10from sympy.functions.combinatorial.numbers import fibonacci
11from sympy.functions.combinatorial.factorials import factorial, subfactorial
12from sympy.functions.special.gamma_functions import gamma
13from sympy.functions.elementary.complexes import Abs
14from sympy.functions.elementary.miscellaneous import Max, Min
15from sympy.functions.elementary.trigonometric import cos, sin
16from sympy.series.limits import Limit
19def difference_delta(expr, n=None, step=1):
20 """Difference Operator.
22 Explanation
23 ===========
25 Discrete analog of differential operator. Given a sequence x[n],
26 returns the sequence x[n + step] - x[n].
28 Examples
29 ========
31 >>> from sympy import difference_delta as dd
32 >>> from sympy.abc import n
33 >>> dd(n*(n + 1), n)
34 2*n + 2
35 >>> dd(n*(n + 1), n, 2)
36 4*n + 6
38 References
39 ==========
41 .. [1] https://reference.wolfram.com/language/ref/DifferenceDelta.html
42 """
43 expr = sympify(expr)
45 if n is None:
46 f = expr.free_symbols
47 if len(f) == 1:
48 n = f.pop()
49 elif len(f) == 0:
50 return S.Zero
51 else:
52 raise ValueError("Since there is more than one variable in the"
53 " expression, a variable must be supplied to"
54 " take the difference of %s" % expr)
55 step = sympify(step)
56 if step.is_number is False or step.is_finite is False:
57 raise ValueError("Step should be a finite number.")
59 if hasattr(expr, '_eval_difference_delta'):
60 result = expr._eval_difference_delta(n, step)
61 if result:
62 return result
64 return expr.subs(n, n + step) - expr
67def dominant(expr, n):
68 """Finds the dominant term in a sum, that is a term that dominates
69 every other term.
71 Explanation
72 ===========
74 If limit(a/b, n, oo) is oo then a dominates b.
75 If limit(a/b, n, oo) is 0 then b dominates a.
76 Otherwise, a and b are comparable.
78 If there is no unique dominant term, then returns ``None``.
80 Examples
81 ========
83 >>> from sympy import Sum
84 >>> from sympy.series.limitseq import dominant
85 >>> from sympy.abc import n, k
86 >>> dominant(5*n**3 + 4*n**2 + n + 1, n)
87 5*n**3
88 >>> dominant(2**n + Sum(k, (k, 0, n)), n)
89 2**n
91 See Also
92 ========
94 sympy.series.limitseq.dominant
95 """
96 terms = Add.make_args(expr.expand(func=True))
97 term0 = terms[-1]
98 comp = [term0] # comparable terms
99 for t in terms[:-1]:
100 r = term0/t
101 e = r.gammasimp()
102 if e == r:
103 e = r.factor()
104 l = limit_seq(e, n)
105 if l is None:
106 return None
107 elif l.is_zero:
108 term0 = t
109 comp = [term0]
110 elif l not in [S.Infinity, S.NegativeInfinity]:
111 comp.append(t)
112 if len(comp) > 1:
113 return None
114 return term0
117def _limit_inf(expr, n):
118 try:
119 return Limit(expr, n, S.Infinity).doit(deep=False)
120 except (NotImplementedError, PoleError):
121 return None
124def _limit_seq(expr, n, trials):
125 from sympy.concrete.summations import Sum
127 for i in range(trials):
128 if not expr.has(Sum):
129 result = _limit_inf(expr, n)
130 if result is not None:
131 return result
133 num, den = expr.as_numer_denom()
134 if not den.has(n) or not num.has(n):
135 result = _limit_inf(expr.doit(), n)
136 if result is not None:
137 return result
138 return None
140 num, den = (difference_delta(t.expand(), n) for t in [num, den])
141 expr = (num / den).gammasimp()
143 if not expr.has(Sum):
144 result = _limit_inf(expr, n)
145 if result is not None:
146 return result
148 num, den = expr.as_numer_denom()
150 num = dominant(num, n)
151 if num is None:
152 return None
154 den = dominant(den, n)
155 if den is None:
156 return None
158 expr = (num / den).gammasimp()
161def limit_seq(expr, n=None, trials=5):
162 """Finds the limit of a sequence as index ``n`` tends to infinity.
164 Parameters
165 ==========
167 expr : Expr
168 SymPy expression for the ``n-th`` term of the sequence
169 n : Symbol, optional
170 The index of the sequence, an integer that tends to positive
171 infinity. If None, inferred from the expression unless it has
172 multiple symbols.
173 trials: int, optional
174 The algorithm is highly recursive. ``trials`` is a safeguard from
175 infinite recursion in case the limit is not easily computed by the
176 algorithm. Try increasing ``trials`` if the algorithm returns ``None``.
178 Admissible Terms
179 ================
181 The algorithm is designed for sequences built from rational functions,
182 indefinite sums, and indefinite products over an indeterminate n. Terms of
183 alternating sign are also allowed, but more complex oscillatory behavior is
184 not supported.
186 Examples
187 ========
189 >>> from sympy import limit_seq, Sum, binomial
190 >>> from sympy.abc import n, k, m
191 >>> limit_seq((5*n**3 + 3*n**2 + 4) / (3*n**3 + 4*n - 5), n)
192 5/3
193 >>> limit_seq(binomial(2*n, n) / Sum(binomial(2*k, k), (k, 1, n)), n)
194 3/4
195 >>> limit_seq(Sum(k**2 * Sum(2**m/m, (m, 1, k)), (k, 1, n)) / (2**n*n), n)
196 4
198 See Also
199 ========
201 sympy.series.limitseq.dominant
203 References
204 ==========
206 .. [1] Computing Limits of Sequences - Manuel Kauers
207 """
209 from sympy.concrete.summations import Sum
210 if n is None:
211 free = expr.free_symbols
212 if len(free) == 1:
213 n = free.pop()
214 elif not free:
215 return expr
216 else:
217 raise ValueError("Expression has more than one variable. "
218 "Please specify a variable.")
219 elif n not in expr.free_symbols:
220 return expr
222 expr = expr.rewrite(fibonacci, S.GoldenRatio)
223 expr = expr.rewrite(factorial, subfactorial, gamma)
224 n_ = Dummy("n", integer=True, positive=True)
225 n1 = Dummy("n", odd=True, positive=True)
226 n2 = Dummy("n", even=True, positive=True)
228 # If there is a negative term raised to a power involving n, or a
229 # trigonometric function, then consider even and odd n separately.
230 powers = (p.as_base_exp() for p in expr.atoms(Pow))
231 if (any(b.is_negative and e.has(n) for b, e in powers) or
232 expr.has(cos, sin)):
233 L1 = _limit_seq(expr.xreplace({n: n1}), n1, trials)
234 if L1 is not None:
235 L2 = _limit_seq(expr.xreplace({n: n2}), n2, trials)
236 if L1 != L2:
237 if L1.is_comparable and L2.is_comparable:
238 return AccumulationBounds(Min(L1, L2), Max(L1, L2))
239 else:
240 return None
241 else:
242 L1 = _limit_seq(expr.xreplace({n: n_}), n_, trials)
243 if L1 is not None:
244 return L1
245 else:
246 if expr.is_Add:
247 limits = [limit_seq(term, n, trials) for term in expr.args]
248 if any(result is None for result in limits):
249 return None
250 else:
251 return Add(*limits)
252 # Maybe the absolute value is easier to deal with (though not if
253 # it has a Sum). If it tends to 0, the limit is 0.
254 elif not expr.has(Sum):
255 lim = _limit_seq(Abs(expr.xreplace({n: n_})), n_, trials)
256 if lim is not None and lim.is_zero:
257 return S.Zero