123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- 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
- from sympy.functions.combinatorial.factorials import factorial
- from sympy.functions.elementary.complexes import (Abs, sign)
- 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 sympy.simplify.powsimp import powsimp
- from sympy.simplify.ratsimp import ratsimp
- from sympy.simplify.simplify import nsimplify, together
- 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 abs(z0) is S.Infinity:
- rv = limit(e.subs(z, 1/z), z, S.Zero, "+" if z0 is S.Infinity else "-")
- if isinstance(rv, Limit):
- return
- elif e.is_Mul or e.is_Add or e.is_Pow or e.is_Function:
- r = []
- 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 in range(len(r)):
- if isinstance(r[ii], AccumBounds):
- r2.append(r[ii])
- 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:
- 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)
- >>> 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 is S.Infinity:
- dir = "-"
- elif z0 is 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 z0 is S.ComplexInfinity:
- raise NotImplementedError("Limits at complex "
- "infinity are not implemented")
- 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(S.Infinity, S.NegativeInfinity, S.ComplexInfinity, S.NaN):
- 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)
- sign_flag = isinstance(expr, sign)
- if abs_flag or sign_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
- elif (sig > 0) == True:
- return expr.args[0] if abs_flag else S.One
- 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
- e = nsimplify(e)
- e = set_signs(e)
- if e.is_meromorphic(z, z0):
- if abs(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 abs(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
- e = powsimp(e)
- if e.is_Pow:
- r = self.pow_heuristics(e)
- if r is not None:
- return r
- else:
- if coeff.has(S.Infinity, S.NegativeInfinity, S.ComplexInfinity):
- return self
- if not coeff.has(z):
- if ex.is_positive:
- return S.Zero
- elif ex == 0:
- return coeff
- elif ex.is_negative:
- if ex.is_integer:
- if cdir == 1 or ex.is_even:
- return S.Infinity*sign(coeff)
- elif cdir == -1:
- return S.NegativeInfinity*sign(coeff)
- else:
- return S.ComplexInfinity
- else:
- if cdir == 1:
- return S.Infinity*sign(coeff)
- elif cdir == -1:
- return S.Infinity*sign(coeff)*S.NegativeOne**ex
- else:
- return S.ComplexInfinity
- # 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:
- if str(dir) == '+-':
- r = gruntz(e, z, z0, '+')
- l = gruntz(e, z, z0, '-')
- if l != r:
- raise ValueError("The limit does not exist since "
- "left hand limit = %s and right hand limit = %s"
- % (l, r))
- else:
- 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
|