123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717 |
- """
- 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
- do not 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 functools import reduce
- from sympy.core import Basic, S, Mul, PoleError
- from sympy.core.cache import cacheit
- from sympy.core.numbers import ilcm, I, oo
- from sympy.core.symbol import Dummy, Wild
- from sympy.core.traversal import bottom_up
- from sympy.functions import log, exp, sign as _sign
- from sympy.series.order import Order
- from sympy.simplify import logcombine
- from sympy.simplify.powsimp import powsimp, powdenest
- from sympy.utilities.misc import debug_decorator as debug
- from sympy.utilities.timeutils import timethis
- timeit = timethis('gruntz')
- 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 (isinstance(a, exp) or (a.is_Pow and a.base == S.Exp1)):
- la = a.exp
- if isinstance(b, Basic) and (isinstance(b, exp) or (b.is_Pow and b.base == S.Exp1)):
- lb = b.exp
- 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.
- Explanation
- ===========
- 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 chosen 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().__repr__() + ', ' + self.rewrites.__repr__()
- def __getitem__(self, key):
- if key not in self:
- self[key] = Dummy()
- return dict.__getitem__(self, key)
- def do_subs(self, e):
- """Substitute the variables with expressions"""
- for expr, var in self.items():
- e = e.xreplace({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.xreplace({var: res[expr]})
- tr[var] = res[expr]
- else:
- res[expr] = var
- for var, rewr in s2.rewrites.items():
- res.rewrites[var] = rewr.xreplace(tr)
- return res, exps
- def copy(self):
- """Create a shallow copy of SubsSet"""
- 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 and e.base != S.Exp1:
- e1 = S.One
- while e.is_Pow:
- b1 = e.base
- e1 *= e.exp
- e = b1
- if b1 == 1:
- return SubsSet(), b1
- if e1.has(x):
- base_lim = limitinf(b1, x)
- if base_lim is S.One:
- return mrv(exp(e1 * (b1 - 1)), x)
- return mrv(exp(e1 * log(b1)), x)
- else:
- s, expr = mrv(b1, x)
- return s, expr**e1
- elif isinstance(e, log):
- s, expr = mrv(e.args[0], x)
- return s, log(expr)
- elif isinstance(e, exp) or (e.is_Pow and e.base == S.Exp1):
- # We know from the theory of this algorithm that exp(log(...)) may always
- # be simplified here, and doing so is vital for termination.
- if isinstance(e.exp, log):
- return mrv(e.exp.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.exp, x)
- if any(_.is_infinite for _ in Mul.make_args(li)):
- s1 = SubsSet()
- e1 = s1[e]
- s2, e2 = mrv(e.exp, 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.exp, 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.")
- 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
- arbitrarily 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.]
- """
- 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):
- e = logcombine(e)
- 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 isinstance(e, exp):
- return 1
- elif e.is_Pow:
- if e.base == S.Exp1:
- return 1
- s = sign(e.base, x)
- if s == 1:
- return 1
- if e.exp.is_Integer:
- return s**e.exp
- elif isinstance(e, 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, leadsimp=False):
- """Limit e(x) for x-> oo.
- Explanation
- ===========
- If ``leadsimp`` is True, an attempt is made to simplify the leading
- term of the series expansion of ``e``. That may succeed even if
- ``e`` cannot be simplified.
- """
- # rewrite e in terms of tractable functions only
- if not e.has(x):
- return e # e is a constant
- if e.has(Order):
- e = e.expand().removeO()
- if not x.is_positive or x.is_integer:
- # We make sure that x.is_positive is True and x.is_integer is None
- # so we get all the correct mathematical behavior from the expression.
- # We need a fresh variable.
- p = Dummy('p', positive=True)
- e = e.subs(x, p)
- x = p
- e = e.rewrite('tractable', deep=True, limitvar=x)
- e = powdenest(e)
- 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:
- if leadsimp:
- c0 = c0.simplify()
- return limitinf(c0, x, leadsimp) # e0=0: lim f = lim c0
- else:
- raise ValueError("{} could not be evaluated".format(sig))
- def moveup2(s, x):
- r = SubsSet()
- for expr, var in s.items():
- r[expr.xreplace({x: exp(x)})] = var
- for var, expr in s.rewrites.items():
- r.rewrites[var] = s.rewrites[var].xreplace({x: exp(x)})
- return r
- def moveup(l, x):
- return [e.xreplace({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.
- """
- for t in e.lseries(x, logx=logx):
- # bottom_up function is required for a specific case - when e is
- # -exp(p/(p + 1)) + exp(-p**2/(p + 1) + p)
- t = bottom_up(t, lambda w:
- getattr(w, 'normal', lambda: w)())
- # And the expression
- # `(-sin(1/x) + sin((x + exp(x))*exp(-x)/x))*exp(x)`
- # from the first test of test_gruntz_eval_special needs to
- # be expanded. But other forms need to be have at least
- # factor_terms applied. `factor` accomplishes both and is
- # faster than using `factor_terms` for the gruntz suite. It
- # does not appear that use of `cancel` is necessary.
- # t = cancel(t, expand=False)
- t = t.factor()
- if t.has(exp) and t.has(log):
- t = powdenest(t)
- if not t.is_zero:
- 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
- return exps, S.Zero
- if x in Omega:
- # move the whole omega up (exponentiate each term):
- Omega_up = moveup2(Omega, x)
- exps_up = moveup([exps], x)[0]
- # NOTE: there is no need to move this down!
- 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", positive=True)
- f, logw = rewrite(exps, Omega, x, w)
- series = calculate_series(f, w, logx=logw)
- try:
- lt = series.leadterm(w, logx=logw)
- except (ValueError, PoleError):
- lt = f.as_coeff_exponent(w)
- # as_coeff_exponent won't always split in required form. It may simply
- # return (f, 0) when a better form may be obtained. Example (-x)**(-pi)
- # can be written as (-1**(-pi), -pi) which as_coeff_exponent does not return
- if lt[0].has(w):
- base = f.as_base_exp()[0].as_coeff_exponent(w)
- ex = f.as_base_exp()[1]
- lt = (base[0]**ex, base[1]*ex)
- return (lt[0].subs(log(w), logw), lt[1])
- 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 __init__(self):
- self.before = []
- self.expr = None
- self.var = None
- 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.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.
- """
- if not isinstance(Omega, SubsSet):
- raise TypeError("Omega should be an instance of SubsSet")
- if len(Omega) == 0:
- raise ValueError("Length cannot be 0")
- # all items in Omega must be exponentials
- for t in Omega.keys():
- if not isinstance(t, 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.exp, 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.exp/g.exp, x)
- if c.is_Rational:
- denominators.append(c.q)
- arg = f.exp
- if var in rewrites:
- if not isinstance(rewrites[var], exp):
- raise ValueError("Value should be exp")
- arg = rewrites[var].args[0]
- O2.append((var, exp((arg - c*g.exp).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 .xreplace() below succeeds:
- # TODO this should not be necessary
- f = powsimp(e, deep=True, combine='exp')
- for a, b in O2:
- f = f.xreplace({a: b})
- for _, var in Omega:
- assert not f.has(var)
- # finally compute the logarithm of w (logw).
- logw = g.exp
- 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.
- Explanation
- ===========
- ``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:
- e0 = e
- elif z0 == -oo:
- e0 = e.subs(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 '-'")
- try:
- r = limitinf(e0, z)
- except ValueError:
- r = limitinf(e0, z, leadsimp=True)
- # 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)
|