1from ..core import (Dummy, Expr, Float, Integer, PoleError, Rational, Symbol, 2 nan, oo) 3from ..core.sympify import sympify 4from ..functions.elementary.trigonometric import cos, sin 5from .gruntz import limitinf 6from .order import Order 7 8 9def limit(expr, z, z0, dir='+'): 10 """ 11 Compute the directional limit of ``expr`` at the point ``z0``. 12 13 Examples 14 ======== 15 16 >>> limit(sin(x)/x, x, 0) 17 1 18 >>> limit(1/x, x, 0, dir='+') 19 oo 20 >>> limit(1/x, x, 0, dir='-') 21 -oo 22 >>> limit(1/x, x, oo) 23 0 24 25 See Also 26 ======== 27 28 Limit 29 30 """ 31 return Limit(expr, z, z0, dir).doit(deep=False) 32 33 34def heuristics(e, z, z0, dir): 35 rv = None 36 37 if isinstance(e, Expr): 38 e = e.expand() 39 40 if abs(z0) is oo: 41 rv = limit(e.subs({z: 1/z}), z, Integer(0), '+' if z0 is oo else '-') 42 if isinstance(rv, Limit): 43 return 44 elif e.is_Mul or e.is_Add or e.is_Pow or e.is_Function: 45 r = [] 46 for a in e.args: 47 l = limit(a, z, z0, dir) 48 if l.has(oo) and (l.func not in (sin, cos) and l.is_finite is None): 49 return 50 elif isinstance(l, Limit): 51 return 52 else: 53 r.append(l) 54 rv = e.func(*r) 55 if rv is nan: 56 return 57 58 return rv 59 60 61class Limit(Expr): 62 r"""Represents a directional limit of ``expr`` at the point ``z0``. 63 64 Parameters 65 ========== 66 67 expr : Expr 68 algebraic expression 69 z : Symbol 70 variable of the ``expr`` 71 z0 : Expr 72 limit point, `z_0` 73 dir : {"+", "-", "real"}, optional 74 For ``dir="+"`` (default) it calculates the limit from the right 75 (`z\to z_0 + 0`) and for ``dir="-"`` the limit from the left (`z\to 76 z_0 - 0`). If ``dir="real"``, the limit is the bidirectional real 77 limit. For infinite ``z0`` (``oo`` or ``-oo``), the ``dir`` argument 78 is determined from the direction of the infinity (i.e., 79 ``dir="-"`` for ``oo``). 80 81 Examples 82 ======== 83 84 >>> Limit(sin(x)/x, x, 0) 85 Limit(sin(x)/x, x, 0) 86 >>> Limit(1/x, x, 0, dir='-') 87 Limit(1/x, x, 0, dir='-') 88 89 """ 90 91 def __new__(cls, e, z, z0, dir='+'): 92 e = sympify(e) 93 z = sympify(z) 94 z0 = sympify(z0) 95 96 if z0 is oo: 97 dir = '-' 98 elif z0 == -oo: 99 dir = '+' 100 101 if isinstance(dir, str): 102 dir = Symbol(dir) 103 elif not isinstance(dir, Symbol): 104 raise TypeError(f'direction must be of type str or Symbol, not {type(dir)}') 105 if str(dir) not in ('+', '-', 'real'): 106 raise ValueError( 107 f"direction must be either '+' or '-' or 'real', not {dir}") 108 109 obj = Expr.__new__(cls) 110 obj._args = (e, z, z0, dir) 111 return obj 112 113 @property 114 def free_symbols(self): 115 e, z, z0 = self.args[:3] 116 return (e.free_symbols - z.free_symbols) | z0.free_symbols 117 118 def doit(self, **hints): 119 """Evaluates limit. 120 121 Notes 122 ===== 123 124 First we handle some trivial cases (i.e. constant), then try 125 Gruntz algorithm (see the :py:mod:`~diofant.series.gruntz` module). 126 127 """ 128 e, z, z0, dir = self.args 129 130 if hints.get('deep', True): 131 e = e.doit(**hints) 132 z = z.doit(**hints) 133 z0 = z0.doit(**hints) 134 135 if str(dir) == 'real': 136 right = limit(e, z, z0, '+') 137 left = limit(e, z, z0, '-') 138 if not left.equals(right): 139 raise PoleError(f'left and right limits for expression {e} at ' 140 f'point {z}={z0} seems to be not equal') 141 else: 142 return right 143 144 use_heuristics = hints.get('heuristics', True) 145 146 has_Floats = e.has(Float) or z0.has(Float) 147 if has_Floats: 148 e = e.subs({k: Rational(k) for k in e.atoms(Float)}) 149 z0 = z0.subs({k: Rational(k) for k in z0.atoms(Float)}) 150 151 if z0.has(z): 152 newz = z.as_dummy() 153 r = limit(e.subs({z: newz}), newz, z0, dir) 154 if isinstance(r, Limit): 155 r = r.subs({newz: z}) 156 return r 157 158 if e == z: 159 return z0 160 161 if not e.has(z): 162 return e 163 164 if z0 is nan: 165 return nan 166 167 if e.is_Relational: 168 ll = limit(e.lhs, z, z0, dir) 169 rl = limit(e.rhs, z, z0, dir) 170 171 if any(isinstance(a, Limit) for a in [ll, rl]): 172 return self 173 else: 174 try: 175 return e.func(ll, rl) 176 except TypeError: 177 return self 178 179 if e.has(Order): 180 e = e.expand() 181 order = e.getO() 182 if order: 183 if (z, z0) in zip(order.variables, order.point): 184 order = limit(order.expr, z, z0, dir) 185 e = e.removeO() + order 186 187 try: 188 # Convert to the limit z->oo and use Gruntz algorithm. 189 newe, newz = e, z 190 if z0 == -oo: 191 newe = e.subs({z: -z}) 192 elif z0 != oo: 193 if str(dir) == '+': 194 newe = e.subs({z: z0 + 1/z}) 195 else: 196 newe = e.subs({z: z0 - 1/z}) 197 198 # We need a fresh variable with correct assumptions. 199 newz = Dummy(z.name, positive=True, finite=True) 200 newe = newe.subs({z: newz}) 201 202 r = limitinf(newe, newz) 203 except (PoleError, ValueError, NotImplementedError): 204 r = None 205 if use_heuristics: 206 r = heuristics(e, z, z0, dir) 207 if r is None: 208 return self 209 210 if has_Floats: 211 r = r.evalf() 212 213 return r 214