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

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 

12 

13def limit(e, z, z0, dir="+"): 

14 """Computes the limit of ``e(z)`` at the point ``z0``. 

15 

16 Parameters 

17 ========== 

18 

19 e : expression, the limit of which is to be taken 

20 

21 z : symbol representing the variable in the limit. 

22 Other symbols are treated as constants. Multivariate limits 

23 are not supported. 

24 

25 z0 : the value toward which ``z`` tends. Can be any expression, 

26 including ``oo`` and ``-oo``. 

27 

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``). 

34 

35 Examples 

36 ======== 

37 

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 

50 

51 Notes 

52 ===== 

53 

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). 

57 

58 See Also 

59 ======== 

60 

61 limit_seq : returns the limit of a sequence. 

62 """ 

63 

64 return Limit(e, z, z0, dir).doit(deep=False) 

65 

66 

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 """ 

74 

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]) 

112 

113 if len(e2) > 0: 

114 e3 = Mul(*e2).simplify() 

115 l = limit(e3, z, z0, dir) 

116 rv = l * Mul(*r2) 

117 

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 

128 

129 

130class Limit(Expr): 

131 """Represents an unevaluated limit. 

132 

133 Examples 

134 ======== 

135 

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='-') 

142 

143 """ 

144 

145 def __new__(cls, e, z, z0, dir="+"): 

146 e = sympify(e) 

147 z = sympify(z) 

148 z0 = sympify(z0) 

149 

150 if z0 in (S.Infinity, S.ImaginaryUnit*S.Infinity): 

151 dir = "-" 

152 elif z0 in (S.NegativeInfinity, S.ImaginaryUnit*S.NegativeInfinity): 

153 dir = "+" 

154 

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) 

166 

167 obj = Expr.__new__(cls) 

168 obj._args = (e, z, z0, dir) 

169 return obj 

170 

171 

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 

179 

180 

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) 

187 

188 ex_lim = limit(e1, z, z0) 

189 base_lim = limit(b1, z, z0) 

190 

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 

197 

198 

199 def doit(self, **hints): 

200 """Evaluates the limit. 

201 

202 Parameters 

203 ========== 

204 

205 deep : bool, optional (default: True) 

206 Invoke the ``doit`` method of the expressions involved before 

207 taking the limit. 

208 

209 hints : optional keyword arguments 

210 To be passed to ``doit`` methods; only used if deep is True. 

211 """ 

212 

213 e, z, z0, dir = self.args 

214 

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)) 

228 

229 if z0 is S.ComplexInfinity: 

230 raise NotImplementedError("Limits at complex " 

231 "infinity are not implemented") 

232 

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 

239 

240 if hints.get('deep', True): 

241 e = e.doit(**hints) 

242 z = z.doit(**hints) 

243 z0 = z0.doit(**hints) 

244 

245 if e == z: 

246 return z0 

247 

248 if not e.has(z): 

249 return e 

250 

251 if z0 is S.NaN: 

252 return S.NaN 

253 

254 if e.has(*_illegal): 

255 return self 

256 

257 if e.is_Order: 

258 return Order(limit(e.expr, z, z0), *e.args[1:]) 

259 

260 cdir = 0 

261 if str(dir) == "+": 

262 cdir = 1 

263 elif str(dir) == "-": 

264 cdir = -1 

265 

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 

287 

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) 

296 

297 

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 

320 

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) 

364 

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) 

371 

372 l = None 

373 

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 

384 

385 return r