Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

""" 

Limits 

====== 

 

Implemented according to the PhD thesis 

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

descriptions of the algorithm including many examples. We summarize here 

the gist of it. 

 

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

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

compared using the properties of L: 

 

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

 

We define >, < ~ according to:: 

 

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

 

we say that: 

- f is greater than any power of g 

- f is more rapidly varying than g 

- f goes to infinity/zero faster than g 

 

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

 

we say that: 

- f is lower than any power of g 

 

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

 

we say that: 

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

powers of the other 

 

Examples 

======== 

:: 

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

2 ~ 3 ~ -5 

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

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

f ~ 1/f 

 

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

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

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

don't compare anything below the class 2~3~-5 (for example log(x) is 

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

 

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

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

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

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

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

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

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

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

 

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

 

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

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

 

for e0>0, lim f = 0 

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

for e0=0, lim f = lim c0 

 

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

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

 

Important functions from the implementation: 

 

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

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

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

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

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

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

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

 

All the functions are really simple and straightforward except 

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

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

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

 

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

thesis. 

 

Debugging 

--------- 

 

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

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

debug prints by defining the environment variable SYMPY_DEBUG. For 

example: 

 

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

 

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

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

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

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

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

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

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

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

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

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

| +-sign(_x, _x) = 1 

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

+-sign(0, _x) = 0 

+-limitinf(1, _x) = 1 

 

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

debug this function to figure out the exact problem. 

 

""" 

from __future__ import print_function, division 

 

from sympy.core import Basic, S, oo, Symbol, I, Dummy, Wild, Mul 

from sympy.functions import log, exp 

from sympy.series.order import Order 

from sympy.simplify.powsimp import powsimp 

from sympy import cacheit 

 

from sympy.core.compatibility import reduce 

 

from sympy.utilities.timeutils import timethis 

timeit = timethis('gruntz') 

 

from sympy.utilities.misc import debug_decorator as debug 

 

 

def compare(a, b, x): 

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

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

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

if isinstance(a, Basic) and a.func is exp: 

la = a.args[0] 

if isinstance(b, Basic) and b.func is exp: 

lb = b.args[0] 

 

c = limitinf(la/lb, x) 

if c == 0: 

return "<" 

elif c.is_infinite: 

return ">" 

else: 

return "=" 

 

 

class SubsSet(dict): 

""" 

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

 

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

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

example:: 

 

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

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

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

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

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

 

is really not what we want! 

 

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

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

 

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

 

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

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

 

d3 + d1 + x. 

 

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

will at this stage be a dictionary:: 

 

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

 

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

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

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

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

 

{d3: exp(x-d2)}. 

 

The function rewrite uses all this information to correctly rewrite our 

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

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

 

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

""" 

def __init__(self): 

self.rewrites = {} 

 

def __repr__(self): 

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

 

def __getitem__(self, key): 

if not key in self: 

self[key] = Dummy() 

return dict.__getitem__(self, key) 

 

def do_subs(self, e): 

for expr, var in self.items(): 

e = e.subs(var, expr) 

return e 

 

def meets(self, s2): 

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

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

 

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

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

res = self.copy() 

tr = {} 

for expr, var in s2.items(): 

if expr in self: 

if exps: 

exps = exps.subs(var, res[expr]) 

tr[var] = res[expr] 

else: 

res[expr] = var 

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

res.rewrites[var] = rewr.subs(tr) 

return res, exps 

 

def copy(self): 

r = SubsSet() 

r.rewrites = self.rewrites.copy() 

for expr, var in self.items(): 

r[expr] = var 

return r 

 

 

@debug 

def mrv(e, x): 

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

and e rewritten in terms of these""" 

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

if not isinstance(e, Basic): 

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

if not e.has(x): 

return SubsSet(), e 

elif e == x: 

s = SubsSet() 

return s, s[x] 

elif e.is_Mul or e.is_Add: 

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

if d.func != e.func: 

s, expr = mrv(d, x) 

return s, e.func(i, expr) 

a, b = d.as_two_terms() 

s1, e1 = mrv(a, x) 

s2, e2 = mrv(b, x) 

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

elif e.is_Pow: 

b, e = e.as_base_exp() 

if e.has(x): 

return mrv(exp(e * log(b)), x) 

else: 

s, expr = mrv(b, x) 

return s, expr**e 

elif e.func is log: 

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

return s, log(expr) 

elif e.func is exp: 

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

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

if e.args[0].func is log: 

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

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

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

# consider the result infinite if any factor is infinite 

li = limitinf(e.args[0], x) 

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

s1 = SubsSet() 

e1 = s1[e] 

s2, e2 = mrv(e.args[0], x) 

su = s1.union(s2)[0] 

su.rewrites[e1] = exp(e2) 

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

else: 

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

return s, exp(expr) 

elif e.is_Function: 

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

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

if len(l2) != 1: 

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

raise NotImplementedError("MRV set computation for functions in" 

" several variables not implemented.") 

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

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

return s, e.func(*args) 

elif e.is_Derivative: 

raise NotImplementedError("MRV set computation for derviatives" 

" not implemented yet.") 

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

raise NotImplementedError( 

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

 

 

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

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

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

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

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

""" 

if not isinstance(f, SubsSet): 

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

if not isinstance(g, SubsSet): 

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

if f == SubsSet(): 

return g, expsg 

elif g == SubsSet(): 

return f, expsf 

elif f.meets(g): 

return union, expsboth 

 

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

if c == ">": 

return f, expsf 

elif c == "<": 

return g, expsg 

else: 

if c != "=": 

raise ValueError("c should be =") 

return union, expsboth 

 

 

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

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

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

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

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

Also returns exps, with the appropriate substitutions made. 

""" 

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

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

u, b, x) 

 

 

@debug 

@cacheit 

@timeit 

def sign(e, x): 

""" 

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

 

:: 

 

e > 0 for x sufficiently large ... 1 

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

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

 

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

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

 

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

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

the same thing as the sign of e.] 

""" 

from sympy import sign as _sign 

if not isinstance(e, Basic): 

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

 

if e.is_positive: 

return 1 

elif e.is_negative: 

return -1 

elif e.is_zero: 

return 0 

 

elif not e.has(x): 

return _sign(e) 

elif e == x: 

return 1 

elif e.is_Mul: 

a, b = e.as_two_terms() 

sa = sign(a, x) 

if not sa: 

return 0 

return sa * sign(b, x) 

elif e.func is exp: 

return 1 

elif e.is_Pow: 

s = sign(e.base, x) 

if s == 1: 

return 1 

if e.exp.is_Integer: 

return s**e.exp 

elif e.func is log: 

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

 

# if all else fails, do it the hard way 

c0, e0 = mrv_leadterm(e, x) 

return sign(c0, x) 

 

 

@debug 

@timeit 

@cacheit 

def limitinf(e, x): 

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

#rewrite e in terms of tractable functions only 

e = e.rewrite('tractable', deep=True) 

 

if not e.has(x): 

return e # e is a constant 

if e.has(Order): 

e = e.expand().removeO() 

if not x.is_positive: 

# We make sure that x.is_positive is True so we 

# get all the correct mathematical behavior from the expression. 

# We need a fresh variable. 

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

e = e.subs(x, p) 

x = p 

c0, e0 = mrv_leadterm(e, x) 

sig = sign(e0, x) 

if sig == 1: 

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

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

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

return c0*oo 

s = sign(c0, x) 

#the leading term shouldn't be 0: 

if s == 0: 

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

return s*oo 

elif sig == 0: 

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

 

 

def moveup2(s, x): 

r = SubsSet() 

for expr, var in s.items(): 

r[expr.subs(x, exp(x))] = var 

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

r.rewrites[var] = s.rewrites[var].subs(x, exp(x)) 

return r 

 

 

def moveup(l, x): 

return [e.subs(x, exp(x)) for e in l] 

 

 

@debug 

@timeit 

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

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

 

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

""" 

from sympy.polys import cancel 

 

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

t = cancel(t) 

 

if t.simplify(): 

break 

 

return t 

 

 

@debug 

@timeit 

@cacheit 

def mrv_leadterm(e, x): 

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

Omega = SubsSet() 

if not e.has(x): 

return (e, S.Zero) 

if Omega == SubsSet(): 

Omega, exps = mrv(e, x) 

if not Omega: 

# e really does not depend on x after simplification 

series = calculate_series(e, x) 

c0, e0 = series.leadterm(x) 

if e0 != 0: 

raise ValueError("e0 should be 0") 

return c0, e0 

if x in Omega: 

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

Omega_up = moveup2(Omega, x) 

e_up = moveup([e], x)[0] 

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

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

e = e_up 

Omega = Omega_up 

exps = exps_up 

# 

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

# a unique dummy is needed in this algorithm 

# 

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

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

# 

w = Dummy("w", real=True, positive=True, finite=True) 

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

series = calculate_series(f, w, logx=logw) 

return series.leadterm(w) 

 

 

def build_expression_tree(Omega, rewrites): 

r""" Helper function for rewrite. 

 

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

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

 

e1 ---> e2 ---> e3 

\ 

-> e4 

 

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

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

 

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

""" 

class Node: 

def ht(self): 

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

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

nodes = {} 

for expr, v in Omega: 

n = Node() 

n.before = [] 

n.var = v 

n.expr = expr 

nodes[v] = n 

for _, v in Omega: 

if v in rewrites: 

n = nodes[v] 

r = rewrites[v] 

for _, v2 in Omega: 

if r.has(v2): 

n.before.append(nodes[v2]) 

 

return nodes 

 

 

@debug 

@timeit 

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

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

Omega ... the mrv set 

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

 

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

for examples and correct results. 

""" 

from sympy import ilcm 

if not isinstance(Omega, SubsSet): 

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

if len(Omega) == 0: 

raise ValueError("Length can not be 0") 

#all items in Omega must be exponentials 

for t in Omega.keys(): 

if not t.func is exp: 

raise ValueError("Value should be exp") 

rewrites = Omega.rewrites 

Omega = list(Omega.items()) 

 

nodes = build_expression_tree(Omega, rewrites) 

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

 

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

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

for g, _ in Omega: 

sig = sign(g.args[0], x) 

if sig != 1 and sig != -1: 

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

if sig == 1: 

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

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

O2 = [] 

denominators = [] 

for f, var in Omega: 

c = limitinf(f.args[0]/g.args[0], x) 

if c.is_Rational: 

denominators.append(c.q) 

arg = f.args[0] 

if var in rewrites: 

if not rewrites[var].func is exp: 

raise ValueError("Value should be exp") 

arg = rewrites[var].args[0] 

O2.append((var, exp((arg - c*g.args[0]).expand())*wsym**c)) 

 

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

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

 

# the following powsimp is necessary to automatically combine exponentials, 

# so that the .subs() below succeeds: 

# TODO this should not be necessary 

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

for a, b in O2: 

f = f.subs(a, b) 

 

for _, var in Omega: 

assert not f.has(var) 

 

#finally compute the logarithm of w (logw). 

logw = g.args[0] 

if sig == 1: 

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

 

# Some parts of sympy have difficulty computing series expansions with 

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

exponent = reduce(ilcm, denominators, 1) 

f = f.subs(wsym, wsym**exponent) 

logw /= exponent 

 

return f, logw 

 

 

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

""" 

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

 

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

 

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

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

(oo or -oo), the dir argument doesn't matter. 

 

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

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

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

""" 

if not z.is_Symbol: 

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

 

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

r = None 

if z0 == oo: 

r = limitinf(e, z) 

elif z0 == -oo: 

r = limitinf(e.subs(z, -z), z) 

else: 

if str(dir) == "-": 

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

elif str(dir) == "+": 

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

else: 

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

r = limitinf(e0, z) 

 

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

# tractable functions in terms of familiar intractable ones. 

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

# but that would take some work to implement. 

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