def _eval_nseries(self, x, n): """ This function does compute series for multivariate functions, but the expansion is always in terms of *one* variable. Examples: >>> from sympy import atan2, O >>> from sympy.abc import x, y >>> atan2(x, y).series(x, n=2) atan2(0, y) + x/y + O(x**2) >>> atan2(x, y).series(y, n=2) atan2(x, 0) - y/x + O(y**2) """ if self.func.nargs is None: raise NotImplementedError('series for user-defined \ functions are not supported.') args = self.args args0 = [t.limit(x, 0) for t in args] if any([t is S.NaN or t.is_bounded is False for t in args0]): raise PoleError("Cannot expand %s around 0" % (args)) if (self.func.nargs == 1 and args0[0]) or self.func.nargs > 1: e = self e1 = e.expand() if e == e1: #for example when e = sin(x+1) or e = sin(cos(x)) #let's try the general algorithm term = e.subs(x, S.Zero) if term.is_bounded is False or term is S.NaN: raise PoleError("Cannot expand %s around 0" % (self)) series = term fact = S.One for i in range(n - 1): i += 1 fact *= Rational(i) e = e.diff(x) subs = e.subs(x, S.Zero) if subs is S.NaN: # try to evaluate a limit if we have to subs = e.limit(x, S.Zero) if subs.is_bounded is False: raise PoleError("Cannot expand %s around 0" % (self)) term = subs * (x**i) / fact term = term.expand() series += term return series + C.Order(x**n, x) return e1.nseries(x, n=n) arg = self.args[0] l = [] g = None for i in xrange(n + 2): g = self.taylor_term(i, arg, g) g = g.nseries(x, n=n) l.append(g) return Add(*l) + C.Order(x**n, x)
def __new__(cls, *args, **assumptions): if len(args) == 0: return cls.identity args = map(_sympify, args) if len(args) == 1: return args[0] if not assumptions.pop('evaluate', True): obj = Expr.__new__(cls, *args, **assumptions) obj.is_commutative = all(a.is_commutative for a in args) return obj c_part, nc_part, order_symbols = cls.flatten(args) if len(c_part) + len(nc_part) <= 1: if c_part: obj = c_part[0] elif nc_part: obj = nc_part[0] else: obj = cls.identity else: obj = Expr.__new__(cls, *(c_part + nc_part), **assumptions) obj.is_commutative = not nc_part if order_symbols is not None: obj = C.Order(obj, *order_symbols) return obj
def extract_leading_order(self, *symbols): """ Returns the leading term and it's order. **Examples** >>> from sympy.abc import x >>> (x+1+1/x**5).extract_leading_order(x) ((x**(-5), O(x**(-5))),) >>> (1+x).extract_leading_order(x) ((1, O(1)),) >>> (x+x**2).extract_leading_order(x) ((x, O(x)),) """ lst = [] seq = [(f, C.Order(f, *symbols)) for f in self.args] for ef,of in seq: for e,o in lst: if o.contains(of) and o != of: of = None break if of is None: continue new_lst = [(ef,of)] for e,o in lst: if of.contains(o) and o != of: continue new_lst.append((e,o)) lst = new_lst return tuple(lst)
def yield_lseries(s): """Return terms of lseries one at a time.""" for si in s: if not si.is_Add: yield si continue # yield terms 1 at a time if possible # by increasing order until all the # terms have been returned yielded = 0 o = C.Order(si)*x ndid = 0 ndo = len(si.args) while 1: do = (si - yielded + o).removeO() o *= x if not do or do.is_Order: continue if do.is_Add: ndid += len(do.args) else: ndid += 1 yield do if ndid == ndo: raise StopIteration yielded += do
def _eval_as_leading_term(self, x): """General method for the leading term""" arg = self.args[0].as_leading_term(x) if C.Order(1, x).contains(arg): return arg else: return self.func(arg)
def _eval_nseries(self, x, n): if self.func.nargs != 1: raise NotImplementedError('series for user-defined and \ multi-arg functions are not supported.') arg = self.args[0] arg0 = arg.limit(x, 0) from sympy import oo if arg0 == S.NaN or arg0.is_bounded == False: raise PoleError("Cannot expand %s around 0" % (arg)) if arg0: e = self e1 = e.expand() if e == e1: #for example when e = sin(x+1) or e = sin(cos(x)) #let's try the general algorithm term = e.subs(x, S.Zero) if arg0 == S.NaN or term.is_bounded == False: raise PoleError("Cannot expand %s around 0" % (self)) series = term fact = S.One for i in range(n - 1): i += 1 fact *= Rational(i) e = e.diff(x) subs = e.subs(x, S.Zero) if subs == S.NaN: # try to evaluate a limit if we have to subs = e.limit(x, S.Zero) if subs.is_bounded == False: raise PoleError("Cannot expand %s around 0" % (self)) term = subs * (x**i) / fact term = term.expand() series += term return series + C.Order(x**n, x) return e1.nseries(x, n=n) l = [] g = None for i in xrange(n + 2): g = self.taylor_term(i, arg, g) g = g.nseries(x, n=n) l.append(g) return Add(*l) + C.Order(x**n, x)
def _eval_as_leading_term(self, x): coeff, terms = self.as_coeff_add(x) has_unbounded = bool([f for f in self.args if f.is_unbounded]) if has_unbounded: if isinstance(terms, Basic): terms = terms.args terms = [f for f in terms if not f.is_bounded] if coeff is not S.Zero: o = C.Order(x) else: o = C.Order(terms[0] * x, x) n = 1 s = self.nseries(x, n=n) while s.is_Order: n += 1 s = self.nseries(x, n=n) if s.is_Add: s = s.removeO() if s.is_Add: lst = s.extract_leading_order(x) return Add(*[e for (e, f) in lst]) return s.as_leading_term(x)
def __new__(cls, *args, **options): args = map(_sympify, args) args = [a for a in args if a is not cls.identity] if not options.pop('evaluate', True): return cls._from_args(args) if len(args) == 0: return cls.identity if len(args) == 1: return args[0] c_part, nc_part, order_symbols = cls.flatten(args) obj = cls._from_args(c_part + nc_part, not nc_part) if order_symbols is not None: return C.Order(obj, *order_symbols) return obj
def __new__(cls, *args, **options): if len(args) == 0: return cls.identity args = map(_sympify, args) try: args = [a for a in args if a is not cls.identity] except AttributeError: pass if len(args) == 0: # recheck after filtering return cls.identity if len(args) == 1: return args[0] if not options.pop('evaluate', True): obj = Expr.__new__(cls, *args) obj.is_commutative = all(a.is_commutative for a in args) return obj c_part, nc_part, order_symbols = cls.flatten(args) if len(c_part) + len(nc_part) <= 1: if c_part: obj = c_part[0] elif nc_part: obj = nc_part[0] else: obj = cls.identity else: obj = Expr.__new__(cls, *(c_part + nc_part)) obj.is_commutative = not nc_part if order_symbols is not None: obj = C.Order(obj, *order_symbols) return obj
def _eval_nseries(self, x, n, logx): # NOTE! This function is an important part of the gruntz algorithm # for computing limits. It has to return a generalized power # series with coefficients in C(log, log(x)). In more detail: # It has to return an expression # c_0*x**e_0 + c_1*x**e_1 + ... (finitely many terms) # where e_i are numbers (not necessarily integers) and c_i are # expressions involving only numbers, the log function, and log(x). from sympy import powsimp, collect, exp, log, O, ceiling b, e = self.args if e.is_Integer: if e > 0: # positive integer powers are easy to expand, e.g.: # sin(x)**4 = (x-x**3/3+...)**4 = ... return expand_multinomial(Pow(b._eval_nseries(x, n=n, logx=logx), e), deep=False) elif e is S.NegativeOne: # this is also easy to expand using the formula: # 1/(1 + x) = 1 - x + x**2 - x**3 ... # so we need to rewrite base to the form "1+x" b = b._eval_nseries(x, n=n, logx=logx) prefactor = b.as_leading_term(x) # express "rest" as: rest = 1 + k*x**l + ... + O(x**n) rest = expand_mul((b - prefactor)/prefactor) if rest == 0: # if prefactor == w**4 + x**2*w**4 + 2*x*w**4, we need to # factor the w**4 out using collect: return 1/collect(prefactor, x) if rest.is_Order: return 1/prefactor + rest/prefactor n2 = rest.getn() if n2 is not None: n = n2 # remove the O - powering this is slow if logx is not None: rest = rest.removeO() k, l = rest.leadterm(x) if l.is_Rational and l > 0: pass elif l.is_number and l > 0: l = l.evalf() else: raise NotImplementedError() terms = [1/prefactor] for m in xrange(1, ceiling(n/l)): new_term = terms[-1]*(-rest) if new_term.is_Pow: new_term = new_term._eval_expand_multinomial( deep=False) else: new_term = expand_mul(new_term, deep=False) terms.append(new_term) # Append O(...), we know the order. if n2 is None or logx is not None: terms.append(O(x**n)) return powsimp(Add(*terms), deep=True, combine='exp') else: # negative powers are rewritten to the cases above, for # example: # sin(x)**(-4) = 1/( sin(x)**4) = ... # and expand the denominator: denominator = (b**(-e))._eval_nseries(x, n=n, logx=logx) if 1/denominator == self: return self # now we have a type 1/f(x), that we know how to expand return (1/denominator)._eval_nseries(x, n=n, logx=logx) if e.has(Symbol): return exp(e*log(b))._eval_nseries(x, n=n, logx=logx) # see if the base is as simple as possible bx = b while bx.is_Pow and bx.exp.is_Rational: bx = bx.base if bx == x: return self # work for b(x)**e where e is not an Integer and does not contain x # and hopefully has no other symbols def e2int(e): """return the integer value (if possible) of e and a flag indicating whether it is bounded or not.""" n = e.limit(x, 0) unbounded = n.is_unbounded if not unbounded: # XXX was int or floor intended? int used to behave like floor # so int(-Rational(1, 2)) returned -1 rather than int's 0 try: n = int(n) except TypeError: #well, the n is something more complicated (like 1+log(2)) try: n = int(n.evalf()) + 1 # XXX why is 1 being added? except TypeError: pass # hope that base allows this to be resolved n = _sympify(n) return n, unbounded order = O(x**n, x) ei, unbounded = e2int(e) b0 = b.limit(x, 0) if unbounded and (b0 is S.One or b0.has(Symbol)): # XXX what order if b0 is S.One: resid = (b - 1) if resid.is_positive: return S.Infinity elif resid.is_negative: return S.Zero raise ValueError('cannot determine sign of %s' % resid) return b0**ei if (b0 is S.Zero or b0.is_unbounded): if unbounded is not False: return b0**e # XXX what order if not ei.is_number: # if not, how will we proceed? raise ValueError( 'expecting numerical exponent but got %s' % ei) nuse = n - ei if e.is_real and e.is_positive: lt = b.as_leading_term(x) # Try to correct nuse (= m) guess from: # (lt + rest + O(x**m))**e = # lt**e*(1 + rest/lt + O(x**m)/lt)**e = # lt**e + ... + O(x**m)*lt**(e - 1) = ... + O(x**n) try: cf = C.Order(lt, x).getn() nuse = ceiling(n - cf*(e - 1)) except NotImplementedError: pass bs = b._eval_nseries(x, n=nuse, logx=logx) terms = bs.removeO() if terms.is_Add: bs = terms lt = terms.as_leading_term(x) # bs -> lt + rest -> lt*(1 + (bs/lt - 1)) return ((Pow(lt, e) * Pow((bs/lt).expand(), e).nseries( x, n=nuse, logx=logx)).expand() + order) if bs.is_Add: from sympy import O # So, bs + O() == terms c = Dummy('c') res = [] for arg in bs.args: if arg.is_Order: arg = c*arg.expr res.append(arg) bs = Add(*res) rv = (bs**e).series(x).subs(c, O(1)) rv += order return rv rv = bs**e if terms != bs: rv += order return rv # either b0 is bounded but neither 1 nor 0 or e is unbounded # b -> b0 + (b-b0) -> b0 * (1 + (b/b0-1)) o2 = order*(b0**-e) z = (b/b0 - 1) o = O(z, x) #r = self._compute_oseries3(z, o2, self.taylor_term) if o is S.Zero or o2 is S.Zero: unbounded = True else: if o.expr.is_number: e2 = log(o2.expr*x)/log(x) else: e2 = log(o2.expr)/log(o.expr) n, unbounded = e2int(e2) if unbounded: # requested accuracy gives infinite series, # order is probably non-polynomial e.g. O(exp(-1/x), x). r = 1 + z else: l = [] g = None for i in xrange(n + 2): g = self.taylor_term(i, z, g) g = g.nseries(x, n=n, logx=logx) l.append(g) r = Add(*l) return r*b0**e + order
def _eval_nseries(self, x, n, logx): """ This function does compute series for multivariate functions, but the expansion is always in terms of *one* variable. Examples: >>> from sympy import atan2, O >>> from sympy.abc import x, y >>> atan2(x, y).series(x, n=2) atan2(0, y) + x/y + O(x**2) >>> atan2(x, y).series(y, n=2) atan2(x, 0) - y/x + O(y**2) This function also computes asymptotic expansions, if necessary and possible: >>> from sympy import loggamma >>> loggamma(1/x)._eval_nseries(x,0,None) log(x)/2 - log(x)/x - 1/x + O(1) """ if self.func.nargs is None: raise NotImplementedError('series for user-defined \ functions are not supported.') args = self.args args0 = [t.limit(x, 0) for t in args] if any([t.is_bounded == False for t in args0]): from sympy import Dummy, oo, zoo, nan a = [t.compute_leading_term(x, logx=logx) for t in args] a0 = [t.limit(x, 0) for t in a] if any([t.has(oo, -oo, zoo, nan) for t in a0]): return self._eval_aseries(n, args0, x, logx)._eval_nseries(x, n, logx) # Careful: the argument goes to oo, but only logarithmically so. We # are supposed to do a power series expansion "around the # logarithmic term". e.g. # f(1+x+log(x)) # -> f(1+logx) + x*f'(1+logx) + O(x**2) # where 'logx' is given in the argument a = [t._eval_nseries(x, n, logx) for t in args] z = [r - r0 for (r, r0) in zip(a, a0)] p = [Dummy() for t in z] q = [] v = None w = None for ai, zi, pi in zip(a0, z, p): if zi.has(x): if v is not None: raise NotImplementedError q.append(ai + pi) v = pi w = zi else: q.append(ai) e1 = self.func(*q) if v is None: return e1 s = e1._eval_nseries(v, n, logx) o = s.getO() s = s.removeO() s = s.subs(v, zi).expand() + C.Order(o.expr.subs(v, zi), x) return s if (self.func.nargs == 1 and args0[0]) or self.func.nargs > 1: e = self e1 = e.expand() if e == e1: #for example when e = sin(x+1) or e = sin(cos(x)) #let's try the general algorithm term = e.subs(x, S.Zero) if term.is_bounded is False or term is S.NaN: raise PoleError("Cannot expand %s around 0" % (self)) series = term fact = S.One for i in range(n - 1): i += 1 fact *= Rational(i) e = e.diff(x) subs = e.subs(x, S.Zero) if subs is S.NaN: # try to evaluate a limit if we have to subs = e.limit(x, S.Zero) if subs.is_bounded is False: raise PoleError("Cannot expand %s around 0" % (self)) term = subs * (x**i) / fact term = term.expand() series += term return series + C.Order(x**n, x) return e1.nseries(x, n=n, logx=logx) arg = self.args[0] l = [] g = None for i in xrange(n + 2): g = self.taylor_term(i, arg, g) g = g.nseries(x, n=n, logx=logx) l.append(g) return Add(*l) + C.Order(x**n, x)
def series(self, x=None, x0=0, n=6, dir="+"): """ Series expansion of "self" around `x = x0` yielding either terms of the series one by one (the lazy series given when n=None), else all the terms at once when n != None. Note: when n != None, if an O() term is returned then the x in the in it and the entire expression reprsents x - x0, the displacement from x0. (If there is no O() term then the series was exact and x has it's normal meaning.) This is currently necessary since sympy's O() can only represent terms at x0=0. So instead of >> cos(x).series(x0=1, n=2) (1 - x)*sin(1) + cos(1) + O((x - 1)**2) which graphically looks like this: \ .|. . . . | \ . . ---+---------------------- | . . . . | \ x=0 the following is returned instead -x*sin(1) + cos(1) + O(x**2) whose graph is this \ | . .| . . . \ . . -----+\------------------. | . . . . | \ x=0 which is identical to cos(x + 1).series(n=2). Usage: Returns the series expansion of "self" around the point `x = x0` with respect to `x` up to O(x**n) (default n is 6). If `x=None` and `self` is univariate, the univariate symbol will be supplied, otherwise an error will be raised. >>> from sympy import cos, exp >>> from sympy.abc import x, y >>> cos(x).series() 1 - x**2/2 + x**4/24 + O(x**6) >>> cos(x).series(n=4) 1 - x**2/2 + O(x**4) >>> e = cos(x + exp(y)) >>> e.series(y, n=2) -y*sin(1 + x) + cos(1 + x) + O(y**2) >>> e.series(x, n=2) -x*sin(exp(y)) + cos(exp(y)) + O(x**2) If `n=None` then an iterator of the series terms will be returned. >>> term=cos(x).series(n=None) >>> [term.next() for i in range(2)] [1, -x**2/2] For `dir=+` (default) the series is calculated from the right and for `dir=-` the series from the left. For smooth functions this flag will not alter the results. >>> abs(x).series(dir="+") x >>> abs(x).series(dir="-") -x """ if x is None: syms = self.atoms(C.Symbol) if len(syms) > 1: raise ValueError('x must be given for multivariate functions.') x = syms.pop() if not self.has(x): if n is None: return (s for s in [self]) else: return self ## it seems like the following should be doable, but several failures ## then occur. Is this related to issue 1747 et al? See also XPOS below. #if x.is_positive is x.is_negative is None: # # replace x with an x that has a positive assumption # xpos = C.Dummy('x', positive=True) # rv = self.subs(x, xpos).series(xpos, x0, n, dir) # if n is None: # return (s.subs(xpos, x) for s in rv) # else: # return rv.subs(xpos, x) if len(dir) != 1 or dir not in '+-': raise ValueError("Dir must be '+' or '-'") if x0 in [S.Infinity, S.NegativeInfinity]: dir = {S.Infinity: '+', S.NegativeInfinity: '-'}[x0] s = self.subs(x, 1/x).series(x, n=n, dir=dir) if n is None: return (si.subs(x, 1/x) for si in s) # don't include the order term since it will eat the larger terms return s.removeO().subs(x, 1/x) # use rep to shift origin to x0 and change sign (if dir is negative) # and undo the process with rep2 if x0 or dir == '-': if dir == '-': rep = -x + x0 rep2 = -x rep2b = x0 else: rep = x + x0 rep2 = x rep2b = -x0 s = self.subs(x, rep).series(x, x0=0, n=n, dir='+') if n is None: # lseries... return (si.subs(x, rep2 + rep2b) for si in s) # nseries... o = s.getO() or S.Zero s = s.removeO() if o and x0: rep2b = 0 # when O() can handle x0 != 0 this can be removed return s.subs(x, rep2 + rep2b) + o # from here on it's x0=0 and dir='+' handling if n != None: # nseries handling s1 = self._eval_nseries(x, n=n) o = s1.getO() or S.Zero if o: # make sure the requested order is returned ngot = o.getn() if ngot > n: # leave o in its current form (e.g. with x*log(x)) so # it eats terms properly, then replace it below s1 += o.subs(x, x**C.Rational(n, ngot)) elif ngot < n: # increase the requested number of terms to get the desired # number keep increasing (up to 9) until the received order # is different than the original order and then predict how # many additional terms are needed for more in range(1, 9): s1 = self._eval_nseries(x, n=n + more) newn = s1.getn() if newn != ngot: ndo = n + (n - ngot)*more/(newn - ngot) s1 = self._eval_nseries(x, n=ndo) # if this assertion fails then our ndo calculation # needs modification assert s1.getn() == n break else: raise ValueError('Could not calculate %s terms for %s' % (str(n), self)) o = s1.getO() s1 = s1.removeO() else: o = C.Order(x**n) if (s1 + o).removeO() == s1: o = S.Zero return s1 + o else: # lseries handling def yield_lseries(s): """Return terms of lseries one at a time.""" for si in s: if not si.is_Add: yield si continue # yield terms 1 at a time if possible # by increasing order until all the # terms have been returned yielded = 0 o = C.Order(si)*x ndid = 0 ndo = len(si.args) while 1: do = (si - yielded + o).removeO() o *= x if not do or do.is_Order: continue if do.is_Add: ndid += len(do.args) else: ndid += 1 yield do if ndid == ndo: raise StopIteration yielded += do return yield_lseries(self.removeO()._eval_lseries(x))
def _eval_order(self, *symbols): # Order(5, x, y) -> Order(1,x,y) return C.Order(S.One, *symbols)