from sympy.calculus.accumulationbounds import AccumBounds from sympy.core import S, Symbol, Add, sympify, Expr, PoleError, Mul from sympy.core.exprtools import factor_terms from sympy.core.numbers import Float, _illegal from sympy.functions.combinatorial.factorials import factorial from sympy.functions.elementary.complexes import (Abs, sign, arg, re) from sympy.functions.elementary.exponential import (exp, log) from sympy.functions.special.gamma_functions import gamma from sympy.polys import PolynomialError, factor from sympy.series.order import Order from .gruntz import gruntz def limit(e, z, z0, dir="+"): """Computes the limit of ``e(z)`` at the point ``z0``. Parameters ========== e : expression, the limit of which is to be taken z : symbol representing the variable in the limit. Other symbols are treated as constants. Multivariate limits are not supported. z0 : the value toward which ``z`` tends. Can be any expression, including ``oo`` and ``-oo``. dir : string, optional (default: "+") The limit is bi-directional if ``dir="+-"``, from the right (z->z0+) if ``dir="+"``, and from the left (z->z0-) if ``dir="-"``. For infinite ``z0`` (``oo`` or ``-oo``), the ``dir`` argument is determined from the direction of the infinity (i.e., ``dir="-"`` for ``oo``). Examples ======== >>> from sympy import limit, sin, oo >>> from sympy.abc import x >>> limit(sin(x)/x, x, 0) 1 >>> limit(1/x, x, 0) # default dir='+' oo >>> limit(1/x, x, 0, dir="-") -oo >>> limit(1/x, x, 0, dir='+-') zoo >>> limit(1/x, x, oo) 0 Notes ===== First we try some heuristics for easy and frequent cases like "x", "1/x", "x**2" and similar, so that it's fast. For all other cases, we use the Gruntz algorithm (see the gruntz() function). See Also ======== limit_seq : returns the limit of a sequence. """ return Limit(e, z, z0, dir).doit(deep=False) def heuristics(e, z, z0, dir): """Computes the limit of an expression term-wise. Parameters are the same as for the ``limit`` function. Works with the arguments of expression ``e`` one by one, computing the limit of each and then combining the results. This approach works only for simple limits, but it is fast. """ rv = None if z0 is S.Infinity: rv = limit(e.subs(z, 1/z), z, S.Zero, "+") if isinstance(rv, Limit): return elif e.is_Mul or e.is_Add or e.is_Pow or e.is_Function: r = [] from sympy.simplify.simplify import together for a in e.args: l = limit(a, z, z0, dir) if l.has(S.Infinity) and l.is_finite is None: if isinstance(e, Add): m = factor_terms(e) if not isinstance(m, Mul): # try together m = together(m) if not isinstance(m, Mul): # try factor if the previous methods failed m = factor(e) if isinstance(m, Mul): return heuristics(m, z, z0, dir) return return elif isinstance(l, Limit): return elif l is S.NaN: return else: r.append(l) if r: rv = e.func(*r) if rv is S.NaN and e.is_Mul and any(isinstance(rr, AccumBounds) for rr in r): r2 = [] e2 = [] for ii, rval in enumerate(r): if isinstance(rval, AccumBounds): r2.append(rval) else: e2.append(e.args[ii]) if len(e2) > 0: e3 = Mul(*e2).simplify() l = limit(e3, z, z0, dir) rv = l * Mul(*r2) if rv is S.NaN: try: from sympy.simplify.ratsimp import ratsimp rat_e = ratsimp(e) except PolynomialError: return if rat_e is S.NaN or rat_e == e: return return limit(rat_e, z, z0, dir) return rv class Limit(Expr): """Represents an unevaluated limit. Examples ======== >>> from sympy import Limit, sin >>> from sympy.abc import x >>> Limit(sin(x)/x, x, 0) Limit(sin(x)/x, x, 0, dir='+') >>> Limit(1/x, x, 0, dir="-") Limit(1/x, x, 0, dir='-') """ def __new__(cls, e, z, z0, dir="+"): e = sympify(e) z = sympify(z) z0 = sympify(z0) if z0 in (S.Infinity, S.ImaginaryUnit*S.Infinity): dir = "-" elif z0 in (S.NegativeInfinity, S.ImaginaryUnit*S.NegativeInfinity): dir = "+" if(z0.has(z)): raise NotImplementedError("Limits approaching a variable point are" " not supported (%s -> %s)" % (z, z0)) if isinstance(dir, str): dir = Symbol(dir) elif not isinstance(dir, Symbol): raise TypeError("direction must be of type basestring or " "Symbol, not %s" % type(dir)) if str(dir) not in ('+', '-', '+-'): raise ValueError("direction must be one of '+', '-' " "or '+-', not %s" % dir) obj = Expr.__new__(cls) obj._args = (e, z, z0, dir) return obj @property def free_symbols(self): e = self.args[0] isyms = e.free_symbols isyms.difference_update(self.args[1].free_symbols) isyms.update(self.args[2].free_symbols) return isyms def pow_heuristics(self, e): _, z, z0, _ = self.args b1, e1 = e.base, e.exp if not b1.has(z): res = limit(e1*log(b1), z, z0) return exp(res) ex_lim = limit(e1, z, z0) base_lim = limit(b1, z, z0) if base_lim is S.One: if ex_lim in (S.Infinity, S.NegativeInfinity): res = limit(e1*(b1 - 1), z, z0) return exp(res) if base_lim is S.NegativeInfinity and ex_lim is S.Infinity: return S.ComplexInfinity def doit(self, **hints): """Evaluates the limit. Parameters ========== deep : bool, optional (default: True) Invoke the ``doit`` method of the expressions involved before taking the limit. hints : optional keyword arguments To be passed to ``doit`` methods; only used if deep is True. """ e, z, z0, dir = self.args if str(dir) == '+-': r = limit(e, z, z0, dir='+') l = limit(e, z, z0, dir='-') if isinstance(r, Limit) and isinstance(l, Limit): if r.args[0] == l.args[0]: return self if r == l: return l if r.is_infinite and l.is_infinite: return S.ComplexInfinity raise ValueError("The limit does not exist since " "left hand limit = %s and right hand limit = %s" % (l, r)) if z0 is S.ComplexInfinity: raise NotImplementedError("Limits at complex " "infinity are not implemented") if z0.is_infinite: cdir = sign(z0) cdir = cdir/abs(cdir) e = e.subs(z, cdir*z) dir = "-" z0 = S.Infinity if hints.get('deep', True): e = e.doit(**hints) z = z.doit(**hints) z0 = z0.doit(**hints) if e == z: return z0 if not e.has(z): return e if z0 is S.NaN: return S.NaN if e.has(*_illegal): return self if e.is_Order: return Order(limit(e.expr, z, z0), *e.args[1:]) cdir = 0 if str(dir) == "+": cdir = 1 elif str(dir) == "-": cdir = -1 def set_signs(expr): if not expr.args: return expr newargs = tuple(set_signs(arg) for arg in expr.args) if newargs != expr.args: expr = expr.func(*newargs) abs_flag = isinstance(expr, Abs) arg_flag = isinstance(expr, arg) sign_flag = isinstance(expr, sign) if abs_flag or sign_flag or arg_flag: sig = limit(expr.args[0], z, z0, dir) if sig.is_zero: sig = limit(1/expr.args[0], z, z0, dir) if sig.is_extended_real: if (sig < 0) == True: return (-expr.args[0] if abs_flag else S.NegativeOne if sign_flag else S.Pi) elif (sig > 0) == True: return (expr.args[0] if abs_flag else S.One if sign_flag else S.Zero) return expr if e.has(Float): # Convert floats like 0.5 to exact SymPy numbers like S.Half, to # prevent rounding errors which can lead to unexpected execution # of conditional blocks that work on comparisons # Also see comments in https://github.com/sympy/sympy/issues/19453 from sympy.simplify.simplify import nsimplify e = nsimplify(e) e = set_signs(e) if e.is_meromorphic(z, z0): if z0 is S.Infinity: newe = e.subs(z, 1/z) # cdir changes sign as oo- should become 0+ cdir = -cdir else: newe = e.subs(z, z + z0) try: coeff, ex = newe.leadterm(z, cdir=cdir) except ValueError: pass else: if ex > 0: return S.Zero elif ex == 0: return coeff if cdir == 1 or not(int(ex) & 1): return S.Infinity*sign(coeff) elif cdir == -1: return S.NegativeInfinity*sign(coeff) else: return S.ComplexInfinity if z0 is S.Infinity: if e.is_Mul: e = factor_terms(e) newe = e.subs(z, 1/z) # cdir changes sign as oo- should become 0+ cdir = -cdir else: newe = e.subs(z, z + z0) try: coeff, ex = newe.leadterm(z, cdir=cdir) except (ValueError, NotImplementedError, PoleError): # The NotImplementedError catching is for custom functions from sympy.simplify.powsimp import powsimp e = powsimp(e) if e.is_Pow: r = self.pow_heuristics(e) if r is not None: return r try: coeff = newe.as_leading_term(z, cdir=cdir) if coeff != newe and (coeff.has(exp) or coeff.has(S.Exp1)): return gruntz(coeff, z, 0, "-" if re(cdir).is_negative else "+") except (ValueError, NotImplementedError, PoleError): pass else: if isinstance(coeff, AccumBounds) and ex == S.Zero: return coeff if coeff.has(S.Infinity, S.NegativeInfinity, S.ComplexInfinity, S.NaN): return self if not coeff.has(z): if ex.is_positive: return S.Zero elif ex == 0: return coeff elif ex.is_negative: if cdir == 1: return S.Infinity*sign(coeff) elif cdir == -1: return S.NegativeInfinity*sign(coeff)*S.NegativeOne**(S.One + ex) else: return S.ComplexInfinity else: raise NotImplementedError("Not sure of sign of %s" % ex) # gruntz fails on factorials but works with the gamma function # If no factorial term is present, e should remain unchanged. # factorial is defined to be zero for negative inputs (which # differs from gamma) so only rewrite for positive z0. if z0.is_extended_positive: e = e.rewrite(factorial, gamma) l = None try: r = gruntz(e, z, z0, dir) if r is S.NaN or l is S.NaN: raise PoleError() except (PoleError, ValueError): if l is not None: raise r = heuristics(e, z, z0, dir) if r is None: return self return r