1
+ """
2
+ Limits
3
+ ======
4
+
5
+ Implemented according to the PhD thesis
6
+ https://www.cybertester.com/data/gruntz.pdf, which contains very thorough
7
+ descriptions of the algorithm including many examples. We summarize here
8
+ the gist of it.
9
+
10
+ All functions are sorted according to how rapidly varying they are at
11
+ infinity using the following rules. Any two functions f and g can be
12
+ compared using the properties of L:
13
+
14
+ L=lim log|f(x)| / log|g(x)| (for x -> oo)
15
+
16
+ We 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
+
36
+ Examples
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
+
45
+ So we can divide all the functions into comparability classes (x and x^2
46
+ belong to one class, exp(x) and exp(-x) belong to some other class). In
47
+ principle, we could compare any two functions, but in our algorithm, we
48
+ do not compare anything below the class 2~3~-5 (for example log(x) is
49
+ below this), so we set 2~3~-5 as the lowest comparability class.
50
+
51
+ Given the function f, we find the list of most rapidly varying (mrv set)
52
+ subexpressions of it. This list belongs to the same comparability class.
53
+ Let's say it is {exp(x), exp(2x)}. Using the rule f ~ 1/f we find an
54
+ element "w" (either from the list or a new one) from the same
55
+ comparability class which goes to zero at infinity. In our example we
56
+ set w=exp(-x) (but we could also set w=exp(-2x) or w=exp(-3x) ...). We
57
+ rewrite the mrv set using w, in our case {1/w, 1/w^2}, and substitute it
58
+ into 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
+
62
+ but for x->oo, lim f = lim c0*w^e0, because all the other terms go to zero,
63
+ because 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
+
69
+ We need to recursively compute limits at several places of the algorithm, but
70
+ as is shown in the PhD thesis, it always finishes.
71
+
72
+ Important functions from the implementation:
73
+
74
+ compare(a, b, x) compares "a" and "b" by computing the limit L.
75
+ mrv(e, x) returns list of most rapidly varying (mrv) subexpressions of "e"
76
+ rewrite(e, Omega, x, wsym) rewrites "e" in terms of w
77
+ leadterm(f, x) returns the lowest power term in the series of f
78
+ mrv_leadterm(e, x) returns the lead term (c0, e0) for e
79
+ limitinf(e, x) computes lim e (for x->oo)
80
+ limit(e, z, z0) computes any limit by converting it to the case x->oo
81
+
82
+ All the functions are really simple and straightforward except
83
+ rewrite(), which is the most difficult/complex part of the algorithm.
84
+ When the algorithm fails, the bugs are usually in the series expansion
85
+ (i.e. in SymPy) or in rewrite.
86
+
87
+ This code is almost exact rewrite of the Maple code inside the Gruntz
88
+ thesis.
89
+
90
+ Debugging
91
+ ---------
92
+
93
+ Because the gruntz algorithm is highly recursive, it's difficult to
94
+ figure out what went wrong inside a debugger. Instead, turn on nice
95
+ debug prints by defining the environment variable SYMPY_DEBUG. For
96
+ example:
97
+
98
+ [user@localhost]: SYMPY_DEBUG=True ./bin/isympy
99
+
100
+ In [1]: limit(sin(x)/x, x, 0)
101
+ limitinf(_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
+
115
+ And check manually which line is wrong. Then go to the source code and
116
+ debug this function to figure out the exact problem.
117
+
118
+ """
119
+ from functools import reduce
120
+
121
+ from sympy .core import Basic , S , Mul , PoleError , expand_mul , evaluate
122
+ from sympy .core .cache import cacheit
123
+ from sympy .core .numbers import I , oo
124
+ from sympy .core .symbol import Dummy , Wild , Symbol
125
+ from sympy .core .traversal import bottom_up
126
+ from sympy .core .sorting import ordered
127
+
128
+ from sympy .functions import log , exp , sign , sin
129
+ from sympy .series .order import Order
130
+ from sympy .utilities .exceptions import SymPyDeprecationWarning
131
+ from sympy .utilities .misc import debug_decorator as debug
132
+ from sympy .utilities .timeutils import timethis
133
+
134
+ def mrv (e , x ):
135
+ """
136
+ Calculate the MRV set of the expression.
137
+
138
+ Examples
139
+ ========
140
+
141
+ >>> mrv(log(x - log(x))/log(x), x)
142
+ {x}
143
+
144
+ """
145
+
146
+ if e == x :
147
+ return {x }
148
+ if e .is_Mul or e .is_Add :
149
+ a , b = e .as_two_terms ()
150
+ ans1 = mrv (a , x )
151
+ ans2 = mrv (b , x )
152
+ return mrv_max (mrv (a , x ), mrv (b , x ), x )
153
+ if e .is_Pow :
154
+ return mrv (e .base , x )
155
+ if e .is_Function :
156
+ return reduce (lambda a , b : mrv_max (a , b , x ), (mrv (a , x ) for a in e .args ))
157
+ raise NotImplementedError (f"Can't calculate the MRV of { e } ." )
158
+
159
+ def mrv_max (f , g , x ):
160
+ """Compute the maximum of two MRV sets.
161
+
162
+ Examples
163
+ ========
164
+
165
+ >>> mrv_max({log(x)}, {x**5}, x)
166
+ {x**5}
167
+
168
+ """
169
+
170
+ if not f :
171
+ return g
172
+ if not g :
173
+ return f
174
+ if f & g :
175
+ return f | g
176
+
177
+ def rewrite (e , x , w ):
178
+ r"""
179
+ Rewrites the expression in terms of the MRV subexpression.
180
+
181
+ Parameters
182
+ ==========
183
+
184
+ e : Expr
185
+ an expression
186
+ x : Symbol
187
+ variable of the `e`
188
+ w : Symbol
189
+ The symbol which is going to be used for substitution in place
190
+ of the MRV in `x` subexpression.
191
+
192
+ Returns
193
+ =======
194
+
195
+ tuple
196
+ A pair: rewritten (in `w`) expression and `\log(w)`.
197
+
198
+ Examples
199
+ ========
200
+
201
+ >>> rewrite(exp(x)*log(x), x, y)
202
+ (log(x)/y, -x)
203
+
204
+ """
205
+
206
+ Omega = mrv (e , x )
207
+ if not Omega :
208
+ return e , None # e really does not depend on x
209
+
210
+ if x in Omega :
211
+ # Moving up in the asymptotical scale:
212
+ with evaluate (False ):
213
+ e = e .xreplace ({x : exp (x )})
214
+ Omega = {s .xreplace ({x : exp (x )}) for s in Omega }
215
+
216
+ Omega = list (ordered (Omega , keys = lambda a : - len (mrv (a , x ))))
217
+
218
+ for g in Omega :
219
+ sig = signinf (g .exp , x )
220
+ if sig not in (1 , - 1 ):
221
+ raise NotImplementedError (f'Result depends on the sign of { sig } .' )
222
+
223
+ if sig == 1 :
224
+ w = 1 / w # if g goes to oo, substitute 1/w
225
+
226
+ # Rewrite and substitute subexpressions in the Omega.
227
+ for a in Omega :
228
+ c = limitinf (a .exp / g .exp , x )
229
+ b = exp (a .exp - c * g .exp )* w ** c # exponential must never be expanded here
230
+ with evaluate (False ):
231
+ e = e .xreplace ({a : b })
232
+
233
+ return e , - sig * g .exp
234
+
235
+ @cacheit
236
+ def mrv_leadterm (e , x ):
237
+ """
238
+ Compute the leading term of the series.
239
+
240
+ Returns
241
+ =======
242
+
243
+ tuple
244
+ The leading term `c_0 w^{e_0}` of the series of `e` in terms
245
+ of the most rapidly varying subexpression `w` in form of
246
+ the pair ``(c0, e0)`` of Expr.
247
+
248
+ Examples
249
+ ========
250
+
251
+ >>> leadterm(1/exp(-x + exp(-x)) - exp(x), x)
252
+ (-1, 0)
253
+
254
+ """
255
+
256
+ w = Dummy ('w' , real = True , positive = True )
257
+ e , logw = rewrite (e , x , w )
258
+ return e .leadterm (w )
259
+
260
+ @cacheit
261
+ def signinf (e , x ):
262
+ r"""
263
+ Determine sign of the expression at the infinity.
264
+
265
+ Returns
266
+ =======
267
+
268
+ {1, 0, -1}
269
+ One or minus one, if `e > 0` or `e < 0` for `x` sufficiently
270
+ large and zero if `e` is *constantly* zero for `x\to\infty`.
271
+
272
+ """
273
+
274
+ if not e .has (x ):
275
+ return sign (e )
276
+ if e == x or (e .is_Pow and signinf (e .base , x ) == 1 ):
277
+ return S (1 )
278
+
279
+ @cacheit
280
+ def limitinf (e , x ):
281
+ """
282
+ Compute the limit of the expression at the infinity.
283
+
284
+ Examples
285
+ ========
286
+
287
+ >>> limitinf(exp(x)*(exp(1/x - exp(-x)) - exp(1/x)), x)
288
+ -1
289
+
290
+ """
291
+
292
+ if not e .has (x ):
293
+ return e
294
+
295
+ c0 , e0 = mrv_leadterm (e , x )
296
+ sig = signinf (e0 , x )
297
+ if sig == 1 :
298
+ return Integer (0 )
299
+ if sig == - 1 :
300
+ return signinf (c0 , x )* oo
301
+ if sig == 0 :
302
+ return limitinf (c0 , x )
303
+ raise NotImplementedError (f'Result depends on the sign of { sig } .' )
304
+
305
+
306
+ def gruntz (e , z , z0 , dir = "+" ):
307
+ """
308
+ Compute the limit of e(z) at the point z0 using the Gruntz algorithm.
309
+
310
+ Explanation
311
+ ===========
312
+
313
+ ``z0`` can be any expression, including oo and -oo.
314
+
315
+ For ``dir="+"`` (default) it calculates the limit from the right
316
+ (z->z0+) and for ``dir="-"`` the limit from the left (z->z0-). For infinite z0
317
+ (oo or -oo), the dir argument does not matter.
318
+
319
+ This algorithm is fully described in the module docstring in the gruntz.py
320
+ file. It relies heavily on the series expansion. Most frequently, gruntz()
321
+ is only used if the faster limit() function (which uses heuristics) fails.
322
+ """
323
+
324
+ if str (dir ) == "-" :
325
+ e0 = e .subs (z , z0 - 1 / z )
326
+ elif str (dir ) == "+" :
327
+ e0 = e .subs (z , z0 + 1 / z )
328
+ else :
329
+ raise NotImplementedError ("dir must be '+' or '-'" )
330
+
331
+ r = limitinf (e0 , z )
332
+ return r
333
+
334
+ # tests
335
+ x = Symbol ('x' )
336
+ ans = gruntz (sin (x )/ x , x , 0 )
337
+ print (ans )
0 commit comments