Coverage for /usr/lib/python3/dist-packages/sympy/series/gruntz.py: 13%

360 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1""" 

2Limits 

3====== 

4 

5Implemented according to the PhD thesis 

6https://www.cybertester.com/data/gruntz.pdf, which contains very thorough 

7descriptions of the algorithm including many examples. We summarize here 

8the gist of it. 

9 

10All functions are sorted according to how rapidly varying they are at 

11infinity using the following rules. Any two functions f and g can be 

12compared using the properties of L: 

13 

14L=lim log|f(x)| / log|g(x)| (for x -> oo) 

15 

16We define >, < ~ according to:: 

17 

18 1. f > g .... L=+-oo 

19 

20 we say that: 

21 - f is greater than any power of g 

22 - f is more rapidly varying than g 

23 - f goes to infinity/zero faster than g 

24 

25 2. f < g .... L=0 

26 

27 we say that: 

28 - f is lower than any power of g 

29 

30 3. f ~ g .... L!=0, +-oo 

31 

32 we say that: 

33 - both f and g are bounded from above and below by suitable integral 

34 powers of the other 

35 

36Examples 

37======== 

38:: 

39 2 < x < exp(x) < exp(x**2) < exp(exp(x)) 

40 2 ~ 3 ~ -5 

41 x ~ x**2 ~ x**3 ~ 1/x ~ x**m ~ -x 

42 exp(x) ~ exp(-x) ~ exp(2x) ~ exp(x)**2 ~ exp(x+exp(-x)) 

43 f ~ 1/f 

44 

45So we can divide all the functions into comparability classes (x and x^2 

46belong to one class, exp(x) and exp(-x) belong to some other class). In 

47principle, we could compare any two functions, but in our algorithm, we 

48do not compare anything below the class 2~3~-5 (for example log(x) is 

49below this), so we set 2~3~-5 as the lowest comparability class. 

50 

51Given the function f, we find the list of most rapidly varying (mrv set) 

52subexpressions of it. This list belongs to the same comparability class. 

53Let's say it is {exp(x), exp(2x)}. Using the rule f ~ 1/f we find an 

54element "w" (either from the list or a new one) from the same 

55comparability class which goes to zero at infinity. In our example we 

56set w=exp(-x) (but we could also set w=exp(-2x) or w=exp(-3x) ...). We 

57rewrite the mrv set using w, in our case {1/w, 1/w^2}, and substitute it 

58into f. Then we expand f into a series in w:: 

59 

60 f = c0*w^e0 + c1*w^e1 + ... + O(w^en), where e0<e1<...<en, c0!=0 

61 

62but for x->oo, lim f = lim c0*w^e0, because all the other terms go to zero, 

63because w goes to zero faster than the ci and ei. So:: 

64 

65 for e0>0, lim f = 0 

66 for e0<0, lim f = +-oo (the sign depends on the sign of c0) 

67 for e0=0, lim f = lim c0 

68 

69We need to recursively compute limits at several places of the algorithm, but 

70as is shown in the PhD thesis, it always finishes. 

71 

72Important functions from the implementation: 

73 

74compare(a, b, x) compares "a" and "b" by computing the limit L. 

75mrv(e, x) returns list of most rapidly varying (mrv) subexpressions of "e" 

76rewrite(e, Omega, x, wsym) rewrites "e" in terms of w 

77leadterm(f, x) returns the lowest power term in the series of f 

78mrv_leadterm(e, x) returns the lead term (c0, e0) for e 

79limitinf(e, x) computes lim e (for x->oo) 

80limit(e, z, z0) computes any limit by converting it to the case x->oo 

81 

82All the functions are really simple and straightforward except 

83rewrite(), which is the most difficult/complex part of the algorithm. 

84When the algorithm fails, the bugs are usually in the series expansion 

85(i.e. in SymPy) or in rewrite. 

86 

87This code is almost exact rewrite of the Maple code inside the Gruntz 

88thesis. 

89 

90Debugging 

91--------- 

92 

93Because the gruntz algorithm is highly recursive, it's difficult to 

94figure out what went wrong inside a debugger. Instead, turn on nice 

95debug prints by defining the environment variable SYMPY_DEBUG. For 

96example: 

97 

98[user@localhost]: SYMPY_DEBUG=True ./bin/isympy 

99 

100In [1]: limit(sin(x)/x, x, 0) 

101limitinf(_x*sin(1/_x), _x) = 1 

102+-mrv_leadterm(_x*sin(1/_x), _x) = (1, 0) 

103| +-mrv(_x*sin(1/_x), _x) = set([_x]) 

104| | +-mrv(_x, _x) = set([_x]) 

105| | +-mrv(sin(1/_x), _x) = set([_x]) 

106| | +-mrv(1/_x, _x) = set([_x]) 

107| | +-mrv(_x, _x) = set([_x]) 

108| +-mrv_leadterm(exp(_x)*sin(exp(-_x)), _x, set([exp(_x)])) = (1, 0) 

109| +-rewrite(exp(_x)*sin(exp(-_x)), set([exp(_x)]), _x, _w) = (1/_w*sin(_w), -_x) 

110| +-sign(_x, _x) = 1 

111| +-mrv_leadterm(1, _x) = (1, 0) 

112+-sign(0, _x) = 0 

113+-limitinf(1, _x) = 1 

114 

115And check manually which line is wrong. Then go to the source code and 

116debug this function to figure out the exact problem. 

117 

118""" 

119from functools import reduce 

120 

121from sympy.core import Basic, S, Mul, PoleError, expand_mul 

122from sympy.core.cache import cacheit 

123from sympy.core.numbers import ilcm, I, oo 

124from sympy.core.symbol import Dummy, Wild 

125from sympy.core.traversal import bottom_up 

126 

127from sympy.functions import log, exp, sign as _sign 

128from sympy.series.order import Order 

129from sympy.utilities.exceptions import SymPyDeprecationWarning 

130from sympy.utilities.misc import debug_decorator as debug 

131from sympy.utilities.timeutils import timethis 

132 

133timeit = timethis('gruntz') 

134 

135 

136def compare(a, b, x): 

137 """Returns "<" if a<b, "=" for a == b, ">" for a>b""" 

138 # log(exp(...)) must always be simplified here for termination 

139 la, lb = log(a), log(b) 

140 if isinstance(a, Basic) and (isinstance(a, exp) or (a.is_Pow and a.base == S.Exp1)): 

141 la = a.exp 

142 if isinstance(b, Basic) and (isinstance(b, exp) or (b.is_Pow and b.base == S.Exp1)): 

143 lb = b.exp 

144 

145 c = limitinf(la/lb, x) 

146 if c == 0: 

147 return "<" 

148 elif c.is_infinite: 

149 return ">" 

150 else: 

151 return "=" 

152 

153 

154class SubsSet(dict): 

155 """ 

156 Stores (expr, dummy) pairs, and how to rewrite expr-s. 

157 

158 Explanation 

159 =========== 

160 

161 The gruntz algorithm needs to rewrite certain expressions in term of a new 

162 variable w. We cannot use subs, because it is just too smart for us. For 

163 example:: 

164 

165 > Omega=[exp(exp(_p - exp(-_p))/(1 - 1/_p)), exp(exp(_p))] 

166 > O2=[exp(-exp(_p) + exp(-exp(-_p))*exp(_p)/(1 - 1/_p))/_w, 1/_w] 

167 > e = exp(exp(_p - exp(-_p))/(1 - 1/_p)) - exp(exp(_p)) 

168 > e.subs(Omega[0],O2[0]).subs(Omega[1],O2[1]) 

169 -1/w + exp(exp(p)*exp(-exp(-p))/(1 - 1/p)) 

170 

171 is really not what we want! 

172 

173 So we do it the hard way and keep track of all the things we potentially 

174 want to substitute by dummy variables. Consider the expression:: 

175 

176 exp(x - exp(-x)) + exp(x) + x. 

177 

178 The mrv set is {exp(x), exp(-x), exp(x - exp(-x))}. 

179 We introduce corresponding dummy variables d1, d2, d3 and rewrite:: 

180 

181 d3 + d1 + x. 

182 

183 This class first of all keeps track of the mapping expr->variable, i.e. 

184 will at this stage be a dictionary:: 

185 

186 {exp(x): d1, exp(-x): d2, exp(x - exp(-x)): d3}. 

187 

188 [It turns out to be more convenient this way round.] 

189 But sometimes expressions in the mrv set have other expressions from the 

190 mrv set as subexpressions, and we need to keep track of that as well. In 

191 this case, d3 is really exp(x - d2), so rewrites at this stage is:: 

192 

193 {d3: exp(x-d2)}. 

194 

195 The function rewrite uses all this information to correctly rewrite our 

196 expression in terms of w. In this case w can be chosen to be exp(-x), 

197 i.e. d2. The correct rewriting then is:: 

198 

199 exp(-w)/w + 1/w + x. 

200 """ 

201 def __init__(self): 

202 self.rewrites = {} 

203 

204 def __repr__(self): 

205 return super().__repr__() + ', ' + self.rewrites.__repr__() 

206 

207 def __getitem__(self, key): 

208 if key not in self: 

209 self[key] = Dummy() 

210 return dict.__getitem__(self, key) 

211 

212 def do_subs(self, e): 

213 """Substitute the variables with expressions""" 

214 for expr, var in self.items(): 

215 e = e.xreplace({var: expr}) 

216 return e 

217 

218 def meets(self, s2): 

219 """Tell whether or not self and s2 have non-empty intersection""" 

220 return set(self.keys()).intersection(list(s2.keys())) != set() 

221 

222 def union(self, s2, exps=None): 

223 """Compute the union of self and s2, adjusting exps""" 

224 res = self.copy() 

225 tr = {} 

226 for expr, var in s2.items(): 

227 if expr in self: 

228 if exps: 

229 exps = exps.xreplace({var: res[expr]}) 

230 tr[var] = res[expr] 

231 else: 

232 res[expr] = var 

233 for var, rewr in s2.rewrites.items(): 

234 res.rewrites[var] = rewr.xreplace(tr) 

235 return res, exps 

236 

237 def copy(self): 

238 """Create a shallow copy of SubsSet""" 

239 r = SubsSet() 

240 r.rewrites = self.rewrites.copy() 

241 for expr, var in self.items(): 

242 r[expr] = var 

243 return r 

244 

245 

246@debug 

247def mrv(e, x): 

248 """Returns a SubsSet of most rapidly varying (mrv) subexpressions of 'e', 

249 and e rewritten in terms of these""" 

250 from sympy.simplify.powsimp import powsimp 

251 e = powsimp(e, deep=True, combine='exp') 

252 if not isinstance(e, Basic): 

253 raise TypeError("e should be an instance of Basic") 

254 if not e.has(x): 

255 return SubsSet(), e 

256 elif e == x: 

257 s = SubsSet() 

258 return s, s[x] 

259 elif e.is_Mul or e.is_Add: 

260 i, d = e.as_independent(x) # throw away x-independent terms 

261 if d.func != e.func: 

262 s, expr = mrv(d, x) 

263 return s, e.func(i, expr) 

264 a, b = d.as_two_terms() 

265 s1, e1 = mrv(a, x) 

266 s2, e2 = mrv(b, x) 

267 return mrv_max1(s1, s2, e.func(i, e1, e2), x) 

268 elif e.is_Pow and e.base != S.Exp1: 

269 e1 = S.One 

270 while e.is_Pow: 

271 b1 = e.base 

272 e1 *= e.exp 

273 e = b1 

274 if b1 == 1: 

275 return SubsSet(), b1 

276 if e1.has(x): 

277 base_lim = limitinf(b1, x) 

278 if base_lim is S.One: 

279 return mrv(exp(e1 * (b1 - 1)), x) 

280 return mrv(exp(e1 * log(b1)), x) 

281 else: 

282 s, expr = mrv(b1, x) 

283 return s, expr**e1 

284 elif isinstance(e, log): 

285 s, expr = mrv(e.args[0], x) 

286 return s, log(expr) 

287 elif isinstance(e, exp) or (e.is_Pow and e.base == S.Exp1): 

288 # We know from the theory of this algorithm that exp(log(...)) may always 

289 # be simplified here, and doing so is vital for termination. 

290 if isinstance(e.exp, log): 

291 return mrv(e.exp.args[0], x) 

292 # if a product has an infinite factor the result will be 

293 # infinite if there is no zero, otherwise NaN; here, we 

294 # consider the result infinite if any factor is infinite 

295 li = limitinf(e.exp, x) 

296 if any(_.is_infinite for _ in Mul.make_args(li)): 

297 s1 = SubsSet() 

298 e1 = s1[e] 

299 s2, e2 = mrv(e.exp, x) 

300 su = s1.union(s2)[0] 

301 su.rewrites[e1] = exp(e2) 

302 return mrv_max3(s1, e1, s2, exp(e2), su, e1, x) 

303 else: 

304 s, expr = mrv(e.exp, x) 

305 return s, exp(expr) 

306 elif e.is_Function: 

307 l = [mrv(a, x) for a in e.args] 

308 l2 = [s for (s, _) in l if s != SubsSet()] 

309 if len(l2) != 1: 

310 # e.g. something like BesselJ(x, x) 

311 raise NotImplementedError("MRV set computation for functions in" 

312 " several variables not implemented.") 

313 s, ss = l2[0], SubsSet() 

314 args = [ss.do_subs(x[1]) for x in l] 

315 return s, e.func(*args) 

316 elif e.is_Derivative: 

317 raise NotImplementedError("MRV set computation for derivatives" 

318 " not implemented yet.") 

319 raise NotImplementedError( 

320 "Don't know how to calculate the mrv of '%s'" % e) 

321 

322 

323def mrv_max3(f, expsf, g, expsg, union, expsboth, x): 

324 """ 

325 Computes the maximum of two sets of expressions f and g, which 

326 are in the same comparability class, i.e. max() compares (two elements of) 

327 f and g and returns either (f, expsf) [if f is larger], (g, expsg) 

328 [if g is larger] or (union, expsboth) [if f, g are of the same class]. 

329 """ 

330 if not isinstance(f, SubsSet): 

331 raise TypeError("f should be an instance of SubsSet") 

332 if not isinstance(g, SubsSet): 

333 raise TypeError("g should be an instance of SubsSet") 

334 if f == SubsSet(): 

335 return g, expsg 

336 elif g == SubsSet(): 

337 return f, expsf 

338 elif f.meets(g): 

339 return union, expsboth 

340 

341 c = compare(list(f.keys())[0], list(g.keys())[0], x) 

342 if c == ">": 

343 return f, expsf 

344 elif c == "<": 

345 return g, expsg 

346 else: 

347 if c != "=": 

348 raise ValueError("c should be =") 

349 return union, expsboth 

350 

351 

352def mrv_max1(f, g, exps, x): 

353 """Computes the maximum of two sets of expressions f and g, which 

354 are in the same comparability class, i.e. mrv_max1() compares (two elements of) 

355 f and g and returns the set, which is in the higher comparability class 

356 of the union of both, if they have the same order of variation. 

357 Also returns exps, with the appropriate substitutions made. 

358 """ 

359 u, b = f.union(g, exps) 

360 return mrv_max3(f, g.do_subs(exps), g, f.do_subs(exps), 

361 u, b, x) 

362 

363 

364@debug 

365@cacheit 

366@timeit 

367def sign(e, x): 

368 """ 

369 Returns a sign of an expression e(x) for x->oo. 

370 

371 :: 

372 

373 e > 0 for x sufficiently large ... 1 

374 e == 0 for x sufficiently large ... 0 

375 e < 0 for x sufficiently large ... -1 

376 

377 The result of this function is currently undefined if e changes sign 

378 arbitrarily often for arbitrarily large x (e.g. sin(x)). 

379 

380 Note that this returns zero only if e is *constantly* zero 

381 for x sufficiently large. [If e is constant, of course, this is just 

382 the same thing as the sign of e.] 

383 """ 

384 if not isinstance(e, Basic): 

385 raise TypeError("e should be an instance of Basic") 

386 

387 if e.is_positive: 

388 return 1 

389 elif e.is_negative: 

390 return -1 

391 elif e.is_zero: 

392 return 0 

393 

394 elif not e.has(x): 

395 from sympy.simplify import logcombine 

396 e = logcombine(e) 

397 return _sign(e) 

398 elif e == x: 

399 return 1 

400 elif e.is_Mul: 

401 a, b = e.as_two_terms() 

402 sa = sign(a, x) 

403 if not sa: 

404 return 0 

405 return sa * sign(b, x) 

406 elif isinstance(e, exp): 

407 return 1 

408 elif e.is_Pow: 

409 if e.base == S.Exp1: 

410 return 1 

411 s = sign(e.base, x) 

412 if s == 1: 

413 return 1 

414 if e.exp.is_Integer: 

415 return s**e.exp 

416 elif isinstance(e, log): 

417 return sign(e.args[0] - 1, x) 

418 

419 # if all else fails, do it the hard way 

420 c0, e0 = mrv_leadterm(e, x) 

421 return sign(c0, x) 

422 

423 

424@debug 

425@timeit 

426@cacheit 

427def limitinf(e, x): 

428 """Limit e(x) for x-> oo.""" 

429 # rewrite e in terms of tractable functions only 

430 

431 old = e 

432 if not e.has(x): 

433 return e # e is a constant 

434 from sympy.simplify.powsimp import powdenest 

435 from sympy.calculus.util import AccumBounds 

436 if e.has(Order): 

437 e = e.expand().removeO() 

438 if not x.is_positive or x.is_integer: 

439 # We make sure that x.is_positive is True and x.is_integer is None 

440 # so we get all the correct mathematical behavior from the expression. 

441 # We need a fresh variable. 

442 p = Dummy('p', positive=True) 

443 e = e.subs(x, p) 

444 x = p 

445 e = e.rewrite('tractable', deep=True, limitvar=x) 

446 e = powdenest(e) 

447 if isinstance(e, AccumBounds): 

448 if mrv_leadterm(e.min, x) != mrv_leadterm(e.max, x): 

449 raise NotImplementedError 

450 c0, e0 = mrv_leadterm(e.min, x) 

451 else: 

452 c0, e0 = mrv_leadterm(e, x) 

453 sig = sign(e0, x) 

454 if sig == 1: 

455 return S.Zero # e0>0: lim f = 0 

456 elif sig == -1: # e0<0: lim f = +-oo (the sign depends on the sign of c0) 

457 if c0.match(I*Wild("a", exclude=[I])): 

458 return c0*oo 

459 s = sign(c0, x) 

460 # the leading term shouldn't be 0: 

461 if s == 0: 

462 raise ValueError("Leading term should not be 0") 

463 return s*oo 

464 elif sig == 0: 

465 if c0 == old: 

466 c0 = c0.cancel() 

467 return limitinf(c0, x) # e0=0: lim f = lim c0 

468 else: 

469 raise ValueError("{} could not be evaluated".format(sig)) 

470 

471 

472def moveup2(s, x): 

473 r = SubsSet() 

474 for expr, var in s.items(): 

475 r[expr.xreplace({x: exp(x)})] = var 

476 for var, expr in s.rewrites.items(): 

477 r.rewrites[var] = s.rewrites[var].xreplace({x: exp(x)}) 

478 return r 

479 

480 

481def moveup(l, x): 

482 return [e.xreplace({x: exp(x)}) for e in l] 

483 

484 

485@debug 

486@timeit 

487def calculate_series(e, x, logx=None): 

488 """ Calculates at least one term of the series of ``e`` in ``x``. 

489 

490 This is a place that fails most often, so it is in its own function. 

491 """ 

492 

493 SymPyDeprecationWarning( 

494 feature="calculate_series", 

495 useinstead="series() with suitable n, or as_leading_term", 

496 issue=21838, 

497 deprecated_since_version="1.12" 

498 ).warn() 

499 

500 from sympy.simplify.powsimp import powdenest 

501 

502 for t in e.lseries(x, logx=logx): 

503 # bottom_up function is required for a specific case - when e is 

504 # -exp(p/(p + 1)) + exp(-p**2/(p + 1) + p) 

505 t = bottom_up(t, lambda w: 

506 getattr(w, 'normal', lambda: w)()) 

507 # And the expression 

508 # `(-sin(1/x) + sin((x + exp(x))*exp(-x)/x))*exp(x)` 

509 # from the first test of test_gruntz_eval_special needs to 

510 # be expanded. But other forms need to be have at least 

511 # factor_terms applied. `factor` accomplishes both and is 

512 # faster than using `factor_terms` for the gruntz suite. It 

513 # does not appear that use of `cancel` is necessary. 

514 # t = cancel(t, expand=False) 

515 t = t.factor() 

516 

517 if t.has(exp) and t.has(log): 

518 t = powdenest(t) 

519 

520 if not t.is_zero: 

521 break 

522 

523 return t 

524 

525 

526@debug 

527@timeit 

528@cacheit 

529def mrv_leadterm(e, x): 

530 """Returns (c0, e0) for e.""" 

531 Omega = SubsSet() 

532 if not e.has(x): 

533 return (e, S.Zero) 

534 if Omega == SubsSet(): 

535 Omega, exps = mrv(e, x) 

536 if not Omega: 

537 # e really does not depend on x after simplification 

538 return exps, S.Zero 

539 if x in Omega: 

540 # move the whole omega up (exponentiate each term): 

541 Omega_up = moveup2(Omega, x) 

542 exps_up = moveup([exps], x)[0] 

543 # NOTE: there is no need to move this down! 

544 Omega = Omega_up 

545 exps = exps_up 

546 # 

547 # The positive dummy, w, is used here so log(w*2) etc. will expand; 

548 # a unique dummy is needed in this algorithm 

549 # 

550 # For limits of complex functions, the algorithm would have to be 

551 # improved, or just find limits of Re and Im components separately. 

552 # 

553 w = Dummy("w", positive=True) 

554 f, logw = rewrite(exps, Omega, x, w) 

555 try: 

556 lt = f.leadterm(w, logx=logw) 

557 except (NotImplementedError, PoleError, ValueError): 

558 n0 = 1 

559 _series = Order(1) 

560 incr = S.One 

561 while _series.is_Order: 

562 _series = f._eval_nseries(w, n=n0+incr, logx=logw) 

563 incr *= 2 

564 series = _series.expand().removeO() 

565 try: 

566 lt = series.leadterm(w, logx=logw) 

567 except (NotImplementedError, PoleError, ValueError): 

568 lt = f.as_coeff_exponent(w) 

569 if lt[0].has(w): 

570 base = f.as_base_exp()[0].as_coeff_exponent(w) 

571 ex = f.as_base_exp()[1] 

572 lt = (base[0]**ex, base[1]*ex) 

573 return (lt[0].subs(log(w), logw), lt[1]) 

574 

575 

576def build_expression_tree(Omega, rewrites): 

577 r""" Helper function for rewrite. 

578 

579 We need to sort Omega (mrv set) so that we replace an expression before 

580 we replace any expression in terms of which it has to be rewritten:: 

581 

582 e1 ---> e2 ---> e3 

583 \ 

584 -> e4 

585 

586 Here we can do e1, e2, e3, e4 or e1, e2, e4, e3. 

587 To do this we assemble the nodes into a tree, and sort them by height. 

588 

589 This function builds the tree, rewrites then sorts the nodes. 

590 """ 

591 class Node: 

592 def __init__(self): 

593 self.before = [] 

594 self.expr = None 

595 self.var = None 

596 def ht(self): 

597 return reduce(lambda x, y: x + y, 

598 [x.ht() for x in self.before], 1) 

599 nodes = {} 

600 for expr, v in Omega: 

601 n = Node() 

602 n.var = v 

603 n.expr = expr 

604 nodes[v] = n 

605 for _, v in Omega: 

606 if v in rewrites: 

607 n = nodes[v] 

608 r = rewrites[v] 

609 for _, v2 in Omega: 

610 if r.has(v2): 

611 n.before.append(nodes[v2]) 

612 

613 return nodes 

614 

615 

616@debug 

617@timeit 

618def rewrite(e, Omega, x, wsym): 

619 """e(x) ... the function 

620 Omega ... the mrv set 

621 wsym ... the symbol which is going to be used for w 

622 

623 Returns the rewritten e in terms of w and log(w). See test_rewrite1() 

624 for examples and correct results. 

625 """ 

626 

627 from sympy import AccumBounds 

628 if not isinstance(Omega, SubsSet): 

629 raise TypeError("Omega should be an instance of SubsSet") 

630 if len(Omega) == 0: 

631 raise ValueError("Length cannot be 0") 

632 # all items in Omega must be exponentials 

633 for t in Omega.keys(): 

634 if not isinstance(t, exp): 

635 raise ValueError("Value should be exp") 

636 rewrites = Omega.rewrites 

637 Omega = list(Omega.items()) 

638 

639 nodes = build_expression_tree(Omega, rewrites) 

640 Omega.sort(key=lambda x: nodes[x[1]].ht(), reverse=True) 

641 

642 # make sure we know the sign of each exp() term; after the loop, 

643 # g is going to be the "w" - the simplest one in the mrv set 

644 for g, _ in Omega: 

645 sig = sign(g.exp, x) 

646 if sig != 1 and sig != -1 and not sig.has(AccumBounds): 

647 raise NotImplementedError('Result depends on the sign of %s' % sig) 

648 if sig == 1: 

649 wsym = 1/wsym # if g goes to oo, substitute 1/w 

650 # O2 is a list, which results by rewriting each item in Omega using "w" 

651 O2 = [] 

652 denominators = [] 

653 for f, var in Omega: 

654 c = limitinf(f.exp/g.exp, x) 

655 if c.is_Rational: 

656 denominators.append(c.q) 

657 arg = f.exp 

658 if var in rewrites: 

659 if not isinstance(rewrites[var], exp): 

660 raise ValueError("Value should be exp") 

661 arg = rewrites[var].args[0] 

662 O2.append((var, exp((arg - c*g.exp).expand())*wsym**c)) 

663 

664 # Remember that Omega contains subexpressions of "e". So now we find 

665 # them in "e" and substitute them for our rewriting, stored in O2 

666 

667 # the following powsimp is necessary to automatically combine exponentials, 

668 # so that the .xreplace() below succeeds: 

669 # TODO this should not be necessary 

670 from sympy.simplify.powsimp import powsimp 

671 f = powsimp(e, deep=True, combine='exp') 

672 for a, b in O2: 

673 f = f.xreplace({a: b}) 

674 

675 for _, var in Omega: 

676 assert not f.has(var) 

677 

678 # finally compute the logarithm of w (logw). 

679 logw = g.exp 

680 if sig == 1: 

681 logw = -logw # log(w)->log(1/w)=-log(w) 

682 

683 # Some parts of SymPy have difficulty computing series expansions with 

684 # non-integral exponents. The following heuristic improves the situation: 

685 exponent = reduce(ilcm, denominators, 1) 

686 f = f.subs({wsym: wsym**exponent}) 

687 logw /= exponent 

688 

689 # bottom_up function is required for a specific case - when f is 

690 # -exp(p/(p + 1)) + exp(-p**2/(p + 1) + p). No current simplification 

691 # methods reduce this to 0 while not expanding polynomials. 

692 f = bottom_up(f, lambda w: getattr(w, 'normal', lambda: w)()) 

693 f = expand_mul(f) 

694 

695 return f, logw 

696 

697 

698def gruntz(e, z, z0, dir="+"): 

699 """ 

700 Compute the limit of e(z) at the point z0 using the Gruntz algorithm. 

701 

702 Explanation 

703 =========== 

704 

705 ``z0`` can be any expression, including oo and -oo. 

706 

707 For ``dir="+"`` (default) it calculates the limit from the right 

708 (z->z0+) and for ``dir="-"`` the limit from the left (z->z0-). For infinite z0 

709 (oo or -oo), the dir argument does not matter. 

710 

711 This algorithm is fully described in the module docstring in the gruntz.py 

712 file. It relies heavily on the series expansion. Most frequently, gruntz() 

713 is only used if the faster limit() function (which uses heuristics) fails. 

714 """ 

715 if not z.is_symbol: 

716 raise NotImplementedError("Second argument must be a Symbol") 

717 

718 # convert all limits to the limit z->oo; sign of z is handled in limitinf 

719 r = None 

720 if z0 in (oo, I*oo): 

721 e0 = e 

722 elif z0 in (-oo, -I*oo): 

723 e0 = e.subs(z, -z) 

724 else: 

725 if str(dir) == "-": 

726 e0 = e.subs(z, z0 - 1/z) 

727 elif str(dir) == "+": 

728 e0 = e.subs(z, z0 + 1/z) 

729 else: 

730 raise NotImplementedError("dir must be '+' or '-'") 

731 

732 r = limitinf(e0, z) 

733 

734 # This is a bit of a heuristic for nice results... we always rewrite 

735 # tractable functions in terms of familiar intractable ones. 

736 # It might be nicer to rewrite the exactly to what they were initially, 

737 # but that would take some work to implement. 

738 return r.rewrite('intractable', deep=True)