Skip to content

Commit e3a4a65

Browse files
committed
Step 2
1 parent 9e3ce1f commit e3a4a65

File tree

2 files changed

+335
-1
lines changed

2 files changed

+335
-1
lines changed

integration_tests/gruntz_demo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ def limitinf(e, x):
343343
c0, e0 = mrv_leadterm(e, x)
344344
sig = signinf(e0, x)
345345
if sig == 1:
346-
return Integer(0)
346+
return S(0)
347347
if sig == -1:
348348
return signinf(c0, x)*oo
349349
if sig == 0:

integration_tests/gruntz_demo2.py

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
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

Comments
 (0)