Buckets:
MisterAI/LocalAI_Demo_backends / cpu-diffusers.upgrade-tmp /venv /lib /python3.10 /site-packages /sympy /series /gruntz.py
| """ | |
| Limits | |
| ====== | |
| Implemented according to the PhD thesis | |
| https://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.function import AppliedUndef | |
| from sympy.core.intfunc import ilcm | |
| from sympy.core.numbers import 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.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 | |
| def mrv(e, x): | |
| """Returns a SubsSet of most rapidly varying (mrv) subexpressions of 'e', | |
| and e rewritten in terms of these""" | |
| from sympy.simplify.powsimp import powsimp | |
| 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): | |
| 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 isinstance(e, AppliedUndef): | |
| raise ValueError("MRV set computation for UndefinedFunction is not allowed") | |
| 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 derivatives" | |
| " 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) | |
| 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): | |
| from sympy.simplify import logcombine | |
| 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) and e.args[0].is_positive: | |
| 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) | |
| def limitinf(e, x): | |
| """Limit e(x) for x-> oo.""" | |
| # rewrite e in terms of tractable functions only | |
| old = e | |
| if not e.has(x): | |
| return e # e is a constant | |
| from sympy.simplify.powsimp import powdenest | |
| from sympy.calculus.util import AccumBounds | |
| 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) | |
| if isinstance(e, AccumBounds): | |
| if mrv_leadterm(e.min, x) != mrv_leadterm(e.max, x): | |
| raise NotImplementedError | |
| c0, e0 = mrv_leadterm(e.min, x) | |
| else: | |
| 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 c0 == old: | |
| c0 = c0.cancel() | |
| return limitinf(c0, x) # 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] | |
| 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) | |
| # Ensure expressions of the form exp(log(...)) don't get simplified automatically in the previous steps. | |
| # see: https://github.com/sympy/sympy/issues/15323#issuecomment-478639399 | |
| f = f.replace(lambda f: f.is_Pow and f.has(x), lambda f: exp(log(f.base)*f.exp)) | |
| try: | |
| lt = f.leadterm(w, logx=logw) | |
| except (NotImplementedError, PoleError, ValueError): | |
| n0 = 1 | |
| _series = Order(1) | |
| incr = S.One | |
| while _series.is_Order: | |
| _series = f._eval_nseries(w, n=n0+incr, logx=logw) | |
| incr *= 2 | |
| series = _series.expand().removeO() | |
| try: | |
| lt = series.leadterm(w, logx=logw) | |
| except (NotImplementedError, PoleError, ValueError): | |
| lt = f.as_coeff_exponent(w) | |
| 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 | |
| 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. | |
| """ | |
| from sympy import AccumBounds | |
| 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 and not sig.has(AccumBounds): | |
| 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))*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 | |
| from sympy.simplify.powsimp import powsimp | |
| 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 | |
| # bottom_up function is required for a specific case - when f is | |
| # -exp(p/(p + 1)) + exp(-p**2/(p + 1) + p). No current simplification | |
| # methods reduce this to 0 while not expanding polynomials. | |
| f = bottom_up(f, lambda w: getattr(w, 'normal', lambda: w)()) | |
| 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 does not 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 in (oo, I*oo): | |
| e0 = e | |
| elif z0 in (-oo, -I*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 '-'") | |
| r = limitinf(e0, z) | |
| # 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) | |
Xet Storage Details
- Size:
- 23.5 kB
- Xet hash:
- d659c7bbbd0a7ff0374e325e9b4c3c4386cd6fdfff55be2757e08e904efdb7e5
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.