Coverage for /usr/lib/python3/dist-packages/sympy/series/limits.py: 8%
236 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
1from sympy.calculus.accumulationbounds import AccumBounds
2from sympy.core import S, Symbol, Add, sympify, Expr, PoleError, Mul
3from sympy.core.exprtools import factor_terms
4from sympy.core.numbers import Float, _illegal
5from sympy.functions.combinatorial.factorials import factorial
6from sympy.functions.elementary.complexes import (Abs, sign, arg, re)
7from sympy.functions.elementary.exponential import (exp, log)
8from sympy.functions.special.gamma_functions import gamma
9from sympy.polys import PolynomialError, factor
10from sympy.series.order import Order
11from .gruntz import gruntz
13def limit(e, z, z0, dir="+"):
14 """Computes the limit of ``e(z)`` at the point ``z0``.
16 Parameters
17 ==========
19 e : expression, the limit of which is to be taken
21 z : symbol representing the variable in the limit.
22 Other symbols are treated as constants. Multivariate limits
23 are not supported.
25 z0 : the value toward which ``z`` tends. Can be any expression,
26 including ``oo`` and ``-oo``.
28 dir : string, optional (default: "+")
29 The limit is bi-directional if ``dir="+-"``, from the right
30 (z->z0+) if ``dir="+"``, and from the left (z->z0-) if
31 ``dir="-"``. For infinite ``z0`` (``oo`` or ``-oo``), the ``dir``
32 argument is determined from the direction of the infinity
33 (i.e., ``dir="-"`` for ``oo``).
35 Examples
36 ========
38 >>> from sympy import limit, sin, oo
39 >>> from sympy.abc import x
40 >>> limit(sin(x)/x, x, 0)
41 1
42 >>> limit(1/x, x, 0) # default dir='+'
43 oo
44 >>> limit(1/x, x, 0, dir="-")
45 -oo
46 >>> limit(1/x, x, 0, dir='+-')
47 zoo
48 >>> limit(1/x, x, oo)
49 0
51 Notes
52 =====
54 First we try some heuristics for easy and frequent cases like "x", "1/x",
55 "x**2" and similar, so that it's fast. For all other cases, we use the
56 Gruntz algorithm (see the gruntz() function).
58 See Also
59 ========
61 limit_seq : returns the limit of a sequence.
62 """
64 return Limit(e, z, z0, dir).doit(deep=False)
67def heuristics(e, z, z0, dir):
68 """Computes the limit of an expression term-wise.
69 Parameters are the same as for the ``limit`` function.
70 Works with the arguments of expression ``e`` one by one, computing
71 the limit of each and then combining the results. This approach
72 works only for simple limits, but it is fast.
73 """
75 rv = None
76 if z0 is S.Infinity:
77 rv = limit(e.subs(z, 1/z), z, S.Zero, "+")
78 if isinstance(rv, Limit):
79 return
80 elif e.is_Mul or e.is_Add or e.is_Pow or e.is_Function:
81 r = []
82 from sympy.simplify.simplify import together
83 for a in e.args:
84 l = limit(a, z, z0, dir)
85 if l.has(S.Infinity) and l.is_finite is None:
86 if isinstance(e, Add):
87 m = factor_terms(e)
88 if not isinstance(m, Mul): # try together
89 m = together(m)
90 if not isinstance(m, Mul): # try factor if the previous methods failed
91 m = factor(e)
92 if isinstance(m, Mul):
93 return heuristics(m, z, z0, dir)
94 return
95 return
96 elif isinstance(l, Limit):
97 return
98 elif l is S.NaN:
99 return
100 else:
101 r.append(l)
102 if r:
103 rv = e.func(*r)
104 if rv is S.NaN and e.is_Mul and any(isinstance(rr, AccumBounds) for rr in r):
105 r2 = []
106 e2 = []
107 for ii, rval in enumerate(r):
108 if isinstance(rval, AccumBounds):
109 r2.append(rval)
110 else:
111 e2.append(e.args[ii])
113 if len(e2) > 0:
114 e3 = Mul(*e2).simplify()
115 l = limit(e3, z, z0, dir)
116 rv = l * Mul(*r2)
118 if rv is S.NaN:
119 try:
120 from sympy.simplify.ratsimp import ratsimp
121 rat_e = ratsimp(e)
122 except PolynomialError:
123 return
124 if rat_e is S.NaN or rat_e == e:
125 return
126 return limit(rat_e, z, z0, dir)
127 return rv
130class Limit(Expr):
131 """Represents an unevaluated limit.
133 Examples
134 ========
136 >>> from sympy import Limit, sin
137 >>> from sympy.abc import x
138 >>> Limit(sin(x)/x, x, 0)
139 Limit(sin(x)/x, x, 0, dir='+')
140 >>> Limit(1/x, x, 0, dir="-")
141 Limit(1/x, x, 0, dir='-')
143 """
145 def __new__(cls, e, z, z0, dir="+"):
146 e = sympify(e)
147 z = sympify(z)
148 z0 = sympify(z0)
150 if z0 in (S.Infinity, S.ImaginaryUnit*S.Infinity):
151 dir = "-"
152 elif z0 in (S.NegativeInfinity, S.ImaginaryUnit*S.NegativeInfinity):
153 dir = "+"
155 if(z0.has(z)):
156 raise NotImplementedError("Limits approaching a variable point are"
157 " not supported (%s -> %s)" % (z, z0))
158 if isinstance(dir, str):
159 dir = Symbol(dir)
160 elif not isinstance(dir, Symbol):
161 raise TypeError("direction must be of type basestring or "
162 "Symbol, not %s" % type(dir))
163 if str(dir) not in ('+', '-', '+-'):
164 raise ValueError("direction must be one of '+', '-' "
165 "or '+-', not %s" % dir)
167 obj = Expr.__new__(cls)
168 obj._args = (e, z, z0, dir)
169 return obj
172 @property
173 def free_symbols(self):
174 e = self.args[0]
175 isyms = e.free_symbols
176 isyms.difference_update(self.args[1].free_symbols)
177 isyms.update(self.args[2].free_symbols)
178 return isyms
181 def pow_heuristics(self, e):
182 _, z, z0, _ = self.args
183 b1, e1 = e.base, e.exp
184 if not b1.has(z):
185 res = limit(e1*log(b1), z, z0)
186 return exp(res)
188 ex_lim = limit(e1, z, z0)
189 base_lim = limit(b1, z, z0)
191 if base_lim is S.One:
192 if ex_lim in (S.Infinity, S.NegativeInfinity):
193 res = limit(e1*(b1 - 1), z, z0)
194 return exp(res)
195 if base_lim is S.NegativeInfinity and ex_lim is S.Infinity:
196 return S.ComplexInfinity
199 def doit(self, **hints):
200 """Evaluates the limit.
202 Parameters
203 ==========
205 deep : bool, optional (default: True)
206 Invoke the ``doit`` method of the expressions involved before
207 taking the limit.
209 hints : optional keyword arguments
210 To be passed to ``doit`` methods; only used if deep is True.
211 """
213 e, z, z0, dir = self.args
215 if str(dir) == '+-':
216 r = limit(e, z, z0, dir='+')
217 l = limit(e, z, z0, dir='-')
218 if isinstance(r, Limit) and isinstance(l, Limit):
219 if r.args[0] == l.args[0]:
220 return self
221 if r == l:
222 return l
223 if r.is_infinite and l.is_infinite:
224 return S.ComplexInfinity
225 raise ValueError("The limit does not exist since "
226 "left hand limit = %s and right hand limit = %s"
227 % (l, r))
229 if z0 is S.ComplexInfinity:
230 raise NotImplementedError("Limits at complex "
231 "infinity are not implemented")
233 if z0.is_infinite:
234 cdir = sign(z0)
235 cdir = cdir/abs(cdir)
236 e = e.subs(z, cdir*z)
237 dir = "-"
238 z0 = S.Infinity
240 if hints.get('deep', True):
241 e = e.doit(**hints)
242 z = z.doit(**hints)
243 z0 = z0.doit(**hints)
245 if e == z:
246 return z0
248 if not e.has(z):
249 return e
251 if z0 is S.NaN:
252 return S.NaN
254 if e.has(*_illegal):
255 return self
257 if e.is_Order:
258 return Order(limit(e.expr, z, z0), *e.args[1:])
260 cdir = 0
261 if str(dir) == "+":
262 cdir = 1
263 elif str(dir) == "-":
264 cdir = -1
266 def set_signs(expr):
267 if not expr.args:
268 return expr
269 newargs = tuple(set_signs(arg) for arg in expr.args)
270 if newargs != expr.args:
271 expr = expr.func(*newargs)
272 abs_flag = isinstance(expr, Abs)
273 arg_flag = isinstance(expr, arg)
274 sign_flag = isinstance(expr, sign)
275 if abs_flag or sign_flag or arg_flag:
276 sig = limit(expr.args[0], z, z0, dir)
277 if sig.is_zero:
278 sig = limit(1/expr.args[0], z, z0, dir)
279 if sig.is_extended_real:
280 if (sig < 0) == True:
281 return (-expr.args[0] if abs_flag else
282 S.NegativeOne if sign_flag else S.Pi)
283 elif (sig > 0) == True:
284 return (expr.args[0] if abs_flag else
285 S.One if sign_flag else S.Zero)
286 return expr
288 if e.has(Float):
289 # Convert floats like 0.5 to exact SymPy numbers like S.Half, to
290 # prevent rounding errors which can lead to unexpected execution
291 # of conditional blocks that work on comparisons
292 # Also see comments in https://github.com/sympy/sympy/issues/19453
293 from sympy.simplify.simplify import nsimplify
294 e = nsimplify(e)
295 e = set_signs(e)
298 if e.is_meromorphic(z, z0):
299 if z0 is S.Infinity:
300 newe = e.subs(z, 1/z)
301 # cdir changes sign as oo- should become 0+
302 cdir = -cdir
303 else:
304 newe = e.subs(z, z + z0)
305 try:
306 coeff, ex = newe.leadterm(z, cdir=cdir)
307 except ValueError:
308 pass
309 else:
310 if ex > 0:
311 return S.Zero
312 elif ex == 0:
313 return coeff
314 if cdir == 1 or not(int(ex) & 1):
315 return S.Infinity*sign(coeff)
316 elif cdir == -1:
317 return S.NegativeInfinity*sign(coeff)
318 else:
319 return S.ComplexInfinity
321 if z0 is S.Infinity:
322 if e.is_Mul:
323 e = factor_terms(e)
324 newe = e.subs(z, 1/z)
325 # cdir changes sign as oo- should become 0+
326 cdir = -cdir
327 else:
328 newe = e.subs(z, z + z0)
329 try:
330 coeff, ex = newe.leadterm(z, cdir=cdir)
331 except (ValueError, NotImplementedError, PoleError):
332 # The NotImplementedError catching is for custom functions
333 from sympy.simplify.powsimp import powsimp
334 e = powsimp(e)
335 if e.is_Pow:
336 r = self.pow_heuristics(e)
337 if r is not None:
338 return r
339 try:
340 coeff = newe.as_leading_term(z, cdir=cdir)
341 if coeff != newe and coeff.has(exp):
342 return gruntz(coeff, z, 0, "-" if re(cdir).is_negative else "+")
343 except (ValueError, NotImplementedError, PoleError):
344 pass
345 else:
346 if isinstance(coeff, AccumBounds) and ex == S.Zero:
347 return coeff
348 if coeff.has(S.Infinity, S.NegativeInfinity, S.ComplexInfinity, S.NaN):
349 return self
350 if not coeff.has(z):
351 if ex.is_positive:
352 return S.Zero
353 elif ex == 0:
354 return coeff
355 elif ex.is_negative:
356 if cdir == 1:
357 return S.Infinity*sign(coeff)
358 elif cdir == -1:
359 return S.NegativeInfinity*sign(coeff)*S.NegativeOne**(S.One + ex)
360 else:
361 return S.ComplexInfinity
362 else:
363 raise NotImplementedError("Not sure of sign of %s" % ex)
365 # gruntz fails on factorials but works with the gamma function
366 # If no factorial term is present, e should remain unchanged.
367 # factorial is defined to be zero for negative inputs (which
368 # differs from gamma) so only rewrite for positive z0.
369 if z0.is_extended_positive:
370 e = e.rewrite(factorial, gamma)
372 l = None
374 try:
375 r = gruntz(e, z, z0, dir)
376 if r is S.NaN or l is S.NaN:
377 raise PoleError()
378 except (PoleError, ValueError):
379 if l is not None:
380 raise
381 r = heuristics(e, z, z0, dir)
382 if r is None:
383 return self
385 return r