1 # This is a very simplified (and not that reliable) version of the limit
2 # algorithm that is only used for series expansion. Use this file only in the
3 # series expansion code. Everywere else, use the general and robust limit
4 # algorithm in limits.py.
6 # The idea is to use the general and robust algorithm in limits.py even in the
7 # series expansion, but currently the limits.py is slower and there were some
8 # other bugs (mainly recursion), if it were used in the series expansion. So
9 # currently we use limits_series, until we move to limits.py completely.
11 from sympy
.core
.basic
import Basic
, S
12 from sympy
.core
.methods
import RelMeths
, ArithMeths
13 from sympy
.core
.cache
import cache_it
, cache_it_immutable
15 class Limit(Basic
, RelMeths
, ArithMeths
):
16 """ Find the limit of the expression under process x->xlim.
21 def __new__(cls
, expr
, x
, xlim
, direction
='<', **assumptions
):
22 expr
= Basic
.sympify(expr
)
24 xlim
= Basic
.sympify(xlim
)
25 if not isinstance(x
, Basic
.Symbol
):
26 raise ValueError("Limit 2nd argument must be Symbol instance (got %s)" % (x
))
27 assert isinstance(x
, Basic
.Symbol
),`x`
32 if isinstance(xlim
, Basic
.NegativeInfinity
):
33 xoo
= InfLimit
.limit_process_symbol()
35 xoo
= Basic
.Symbol(x
.name
+ '_oo',dummy
=True,positive
=True,unbounded
=True)
36 return InfLimit(expr
.subs(x
,-xoo
), xoo
)
37 if isinstance(xlim
, Basic
.Infinity
):
38 return InfLimit(expr
, x
)
40 xoo
= InfLimit
.limit_process_symbol()
42 xoo
= Basic
.Symbol(x
.name
+ '_oo',dummy
=True,positive
=True,unbounded
=True)
44 return InfLimit(expr
.subs(x
, xlim
+1/xoo
), xoo
)
46 return InfLimit(expr
.subs(x
, xlim
-1/xoo
), xoo
)
48 raise ValueError("Limit direction must be < or > (got %s)" % (direction
))
50 # XXX This code is currently unreachable
51 obj
= Basic
.__new
__(cls
, expr
, x
, xlim
, **assumptions
)
52 obj
.direction
= direction
55 def _hashable_content(self
):
56 return self
._args
+ (self
.direction
,)
70 def tostr(self
, level
=0):
71 if isinstance(self
.varlim
,(Basic
.Infinity
, Basic
.NegativeInfinity
)): s
= ''
72 elif self
.direction
=='<': s
= '+'
74 r
= 'lim[%s->%s%s](%s)' % (self
.var
.tostr(), self
.varlim
.tostr(),s
,self
.expr
.tostr())
75 if self
.precedence
<= level
: r
= '(%s)' % (r
)
78 class InfLimit(Basic
):
81 def limit_process_symbol():
82 return Basic
.Symbol('xoo', dummy
=True, unbounded
=True, positive
=True)
85 def __new__(cls
, expr
, x
):
86 expr
= orig_expr
= Basic
.sympify(expr
)
87 orig_x
= Basic
.sympify(x
)
88 assert isinstance(orig_x
,Basic
.Symbol
),`orig_x`
90 # handle trivial results
93 elif not orig_expr
.has(orig_x
):
96 x
= InfLimit
.limit_process_symbol()
97 if not orig_expr
.has(x
):
98 expr
= orig_expr
.subs(orig_x
, x
)
102 x
= Basic
.Symbol(orig_x
.name
+ '_oo', dummy
=True, unbounded
=True, positive
=True)
103 expr
= orig_expr
.subs(orig_x
, x
)
106 if hasattr(expr
,'_eval_inflimit'):
107 # support for callbacks
108 result
= getattr(expr
,'_eval_inflimit')(x
)
109 elif isinstance(expr
, Basic
.Add
):
110 result
, factors
= expr
.as_coeff_factors(x
)
112 result
+= f
.inflimit(x
)
113 if isinstance(result
, Basic
.NaN
):
116 elif isinstance(expr
, Basic
.Mul
):
117 result
, terms
= expr
.as_coeff_terms(x
)
119 result
*= t
.inflimit(x
)
120 if isinstance(result
, Basic
.NaN
):
123 elif isinstance(expr
, Basic
.Pow
):
124 if not expr
.exp
.has(x
):
125 result
= expr
.base
.inflimit(x
) ** expr
.exp
126 elif not expr
.base
.has(x
):
127 result
= expr
.base
** expr
.exp
.inflimit(x
)
129 result
= Basic
.exp(expr
.exp
* Basic
.log(expr
.base
)).inflimit(x
)
130 elif isinstance(expr
, Basic
.Function
):
131 # warning: assume that
132 # lim_x f(g1(x),g2(x),..) = f(lim_x g1(x), lim_x g2(x))
133 # if this is incorrect, one must define f._eval_inflimit(x) method
134 result
= expr
.func(*[a
.inflimit(x
) for a
in expr
.args
])
137 result
= mrv_inflimit(expr
, x
)
142 def mrv_inflimit(expr
, x
, _cache
= {}):
143 if _cache
.has_key((expr
, x
)):
144 raise RuntimeError('Detected recursion while computing mrv_inflimit(%s, %s)' % (expr
, x
))
145 _cache
[(expr
, x
)] = 1
148 newexpr
= mrv2(expr
, x
, expr_map
, mrv_map
)
149 if mrv_map
.has_key(x
):
150 t
= Basic
.Temporary(unbounded
=True, positive
=True)
151 r
= mrv_inflimit(expr
.subs(Basic
.log(x
), t
).subs(x
, Basic
.exp(t
)).subs(t
, x
), x
)
152 del _cache
[(expr
, x
)]
154 w
= Basic
.Symbol('w_0',dummy
=True, positive
=True, infinitesimal
=True)
155 germ
, new_mrv_map
= rewrite_mrv_map(mrv_map
, x
, w
)
156 new_expr
= rewrite_expr(newexpr
, germ
, new_mrv_map
, w
)
157 lt
= new_expr
.as_leading_term(w
)
159 lt
= lt
.subs(Basic
.log(w
), -germ
.args
[0])
160 c
,e
= lt
.as_coeff_exponent(w
)
161 assert not c
.has(w
),`c`
164 del _cache
[(expr
, x
)]
167 del _cache
[(expr
, x
)]
170 del _cache
[(expr
, x
)]
171 return Basic
.sign(c
) * S
.Infinity
172 raise RuntimeError('Failed to compute mrv_inflimit(%s, %s), got lt=%s' % (self
, x
, lt
))
175 def cmp_ops_count(e1
,e2
):
176 return cmp(e1
.count_ops(symbolic
=False), e2
.count_ops(symbolic
=False))
179 def mrv_compare(f
, g
, x
):
181 if isinstance(f
, Basic
.exp
): f
= f
.args
[0]
183 if isinstance(g
, Basic
.exp
): g
= g
.args
[0]
185 c
= (f
/g
).inflimit(x
)
188 if isinstance(abs(c
), Basic
.Infinity
):
190 if not c
.is_comparable
:
191 raise ValueError("non-comparable result: %s" % (c
))
194 def mrv2(expr
, x
, d
, md
):
196 Compute a set of most rapidly varying subexpressions of expr with respect to x.
200 mrv2(x + exp(x),x,d) -> x+se, d={x:x, exp(x):se}, md={exp(x):se}
202 if d
.has_key(expr
): return d
[expr
]
208 if isinstance(expr
, (Basic
.Add
, Basic
.Mul
)):
209 r
= expr
.__class
__(*[mrv2(t
, x
, d
, md
) for t
in expr
.args
])
214 if isinstance(expr
, Basic
.Pow
):
215 if not expr
.exp
.has(x
):
216 r
= mrv2(expr
.base
, x
, d
, md
)**expr
.exp
218 r
= mrv2(exp(expr
.exp
* log(expr
.base
)), x
, d
, md
)
221 if isinstance(expr
, Basic
.exp
):
224 r
= exp(mrv2(e
, x
, d
, md
))
225 if isinstance(l
, Basic
.Infinity
):
228 elif isinstance(l
, Basic
.NegativeInfinity
):
230 # rewrite to ensure that exp(e) -> oo
236 # normalize exp(2*e) -> exp(e)
237 coeff
, terms
= en
.as_coeff_terms()
249 coeff
= Basic
.sign(coeff
)
250 if not isinstance(coeff
, Basic
.One
):
251 terms
.insert(0,coeff
)
252 en
= Basic
.Mul(*terms
)
254 #print coeff,terms,nexpr
259 lst
.sort(cmp_ops_count
)
260 c
= mrv_compare(nexpr
, lst
[0], x
)
264 if md
.has_key(nexpr
):
267 tmp
= Basic
.Temporary()
269 r
= expr
.subs(nexpr
, tmp
)
272 if isinstance(expr
, Basic
.Function
):
273 r
= expr
.func(*[mrv2(a
, x
, d
, md
) for a
in expr
.args
])
276 raise NotImplementedError("don't know how to find mrv2(%s,%s)" % (expr
,x
))
278 def rewrite_mrv_map(mrv_map
, x
, w
):
279 germs
= mrv_map
.keys()
280 germs
.sort(cmp_ops_count
)
294 c
= (arg
/garg
).inflimit(x
)
296 Aarg
= Aarg
.subs(g
, 1/w
)
298 new_germ
= A
* w
** -c
302 def rewrite_expr(expr
, germ
, mrv_map
, w
):
303 tmps
= expr
.atoms(Basic
.Temporary
)
312 mrvlog
= Basic
.MrvLog
314 e
= e
.subs(log
, mrvlog
).subs(germ
.args
[0], -log(w
)).subs(mrvlog
, log
)
317 Basic
.singleton
['limit'] = lambda : Limit
320 return e
.limit(x
, x0
)