'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.
Returns the series expansion of "self" around the point ``x = x0``
with respect to ``x`` up to ``O((x - x0)**n, x, x0)`` (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)
>>> cos(x).series(x, x0=1, n=2)
cos(1) - (x - 1)*sin(1) + O((x - 1)**2, (x, 1))
>>> e = cos(x + exp(y))
>>> e.series(y, n=2)
cos(x + 1) - y*sin(x + 1) + O(y**2)
>>> e.series(x, n=2)
cos(exp(y)) - x*sin(exp(y)) + O(x**2)
If ``n=None`` then a generator of the series terms will be returned.
>>> term=cos(x).series(n=None)
>>> [next(term) 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'

def series(self, x=None, x0=0, n=6, dir='+', logx=None):

    from sympy import collect, Dummy, Order, Rational, Symbol
    if (x is None):
        syms = self.atoms(Symbol)
        if (not syms):
            return self
        elif (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
    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)
        return s.subs(x, (1 / x))
    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='+', logx=logx)
        if (n is None):
            return (si.subs(x, (rep2 + rep2b)) for si in s)
        return s.subs(x, (rep2 + rep2b))
    if ((x.is_positive is x.is_negative is None) or (x.is_Symbol is not True)):
        xpos = Dummy('x', positive=True, finite=True)
        rv = self.subs(x, xpos).series(xpos, x0, n, dir, logx=logx)
        if (n is None):
            return (s.subs(xpos, x) for s in rv)
        else:
            return rv.subs(xpos, x)
    if (n is not None):
        s1 = self._eval_nseries(x, n=n, logx=logx)
        o = (s1.getO() or S.Zero)
        if o:
            ngot = o.getn()
            if (ngot > n):
                if (n != 0):
                    s1 += o.subs(x, (x ** Rational(n, ngot)))
                else:
                    s1 += Order(1, x)
            elif (ngot < n):
                for more in range(1, 9):
                    s1 = self._eval_nseries(x, n=(n + more), logx=logx)
                    newn = s1.getn()
                    if (newn != ngot):
                        ndo = (n + (((n - ngot) * more) / (newn - ngot)))
                        s1 = self._eval_nseries(x, n=ndo, logx=logx)
                        while (s1.getn() < n):
                            s1 = self._eval_nseries(x, n=ndo, logx=logx)
                            ndo += 1
                        break
                else:
                    raise ValueError(('Could    not    calculate    %s    terms    for    %s' % (str(n), self)))
                s1 += Order((x ** n), x)
            o = s1.getO()
            s1 = s1.removeO()
        else:
            o = Order((x ** n), x)
            s1done = s1.doit()
            if ((s1done + o).removeO() == s1done):
                o = S.Zero
        try:
            return (collect(s1, x) + o)
        except NotImplementedError:
            return (s1 + o)
    else:
        def yield_lseries(s):
            'Return    terms    of    lseries    one    at    a    time.'
            for si in s:
                if (not si.is_Add):
                    (yield si)
                    continue
                yielded = 0
                o = (Order(si, x) * 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):
                        break
                    yielded += do
        return yield_lseries(self.removeO()._eval_lseries(x, logx=logx))
