Traktor/myenv/Lib/site-packages/sympy/series/limits.py

386 lines
12 KiB
Python
Raw Normal View History

2024-05-23 01:57:24 +02:00
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):
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