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+ The rewritten expression
196+
197+ Examples
198+ ========
199+
200+ >>> rewrite(exp(x)*log(x), x, y)
201+ (log(x)/y, -x)
202+
203+ """
204+
205+ Omega = mrv (e , x )
206+
207+ if x in Omega :
208+ # Moving up in the asymptotical scale:
209+ with evaluate (False ):
210+ e = e .subs (x , exp (x ))
211+ Omega = {s .subs (x , exp (x )) for s in Omega }
212+
213+ Omega = list (ordered (Omega , keys = lambda a : - len (mrv (a , x ))))
214+
215+ for g in Omega :
216+ sig = signinf (g .exp , x )
217+ if sig not in (1 , - 1 ):
218+ raise NotImplementedError (f'Result depends on the sign of { sig } .' )
219+
220+ if sig == 1 :
221+ w = 1 / w # if g goes to oo, substitute 1/w
222+
223+ # Rewrite and substitute subexpressions in the Omega.
224+ for a in Omega :
225+ c = limitinf (a .exp / g .exp , x )
226+ b = exp (a .exp - c * g .exp )* w ** c # exponential must never be expanded here
227+ with evaluate (False ):
228+ e = e .subs (a , b )
229+
230+ return e
231+
232+ @cacheit
233+ def mrv_leadterm (e , x ):
234+ """
235+ Compute the leading term of the series.
236+
237+ Returns
238+ =======
239+
240+ tuple
241+ The leading term `c_0 w^{e_0}` of the series of `e` in terms
242+ of the most rapidly varying subexpression `w` in form of
243+ the pair ``(c0, e0)`` of Expr.
244+
245+ Examples
246+ ========
247+
248+ >>> leadterm(1/exp(-x + exp(-x)) - exp(x), x)
249+ (-1, 0)
250+
251+ """
252+
253+ w = Dummy ('w' , real = True , positive = True )
254+ e = rewrite (e , x , w )
255+ return e .leadterm (w )
256+
257+ @cacheit
258+ def signinf (e , x ):
259+ r"""
260+ Determine sign of the expression at the infinity.
261+
262+ Returns
263+ =======
264+
265+ {1, 0, -1}
266+ One or minus one, if `e > 0` or `e < 0` for `x` sufficiently
267+ large and zero if `e` is *constantly* zero for `x\to\infty`.
268+
269+ """
270+
271+ if not e .has (x ):
272+ return sign (e )
273+ if e == x or (e .is_Pow and signinf (e .base , x ) == 1 ):
274+ return S (1 )
275+
276+ @cacheit
277+ def limitinf (e , x ):
278+ """
279+ Compute the limit of the expression at the infinity.
280+
281+ Examples
282+ ========
283+
284+ >>> limitinf(exp(x)*(exp(1/x - exp(-x)) - exp(1/x)), x)
285+ -1
286+
287+ """
288+
289+ if not e .has (x ):
290+ return e
291+
292+ c0 , e0 = mrv_leadterm (e , x )
293+ sig = signinf (e0 , x )
294+ if sig == 1 :
295+ return Integer (0 )
296+ if sig == - 1 :
297+ return signinf (c0 , x )* oo
298+ if sig == 0 :
299+ return limitinf (c0 , x )
300+ raise NotImplementedError (f'Result depends on the sign of { sig } .' )
301+
302+
303+ def gruntz (e , z , z0 , dir = "+" ):
304+ """
305+ Compute the limit of e(z) at the point z0 using the Gruntz algorithm.
306+
307+ Explanation
308+ ===========
309+
310+ ``z0`` can be any expression, including oo and -oo.
311+
312+ For ``dir="+"`` (default) it calculates the limit from the right
313+ (z->z0+) and for ``dir="-"`` the limit from the left (z->z0-). For infinite z0
314+ (oo or -oo), the dir argument does not matter.
315+
316+ This algorithm is fully described in the module docstring in the gruntz.py
317+ file. It relies heavily on the series expansion. Most frequently, gruntz()
318+ is only used if the faster limit() function (which uses heuristics) fails.
319+ """
320+
321+ if str (dir ) == "-" :
322+ e0 = e .subs (z , z0 - 1 / z )
323+ elif str (dir ) == "+" :
324+ e0 = e .subs (z , z0 + 1 / z )
325+ else :
326+ raise NotImplementedError ("dir must be '+' or '-'" )
327+
328+ r = limitinf (e0 , z )
329+ return r
330+
331+ # tests
332+ x = Symbol ('x' )
333+ ans = gruntz (sin (x )/ x , x , 0 )
334+ print (ans )
0 commit comments