Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
 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
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
 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)
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
    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
Exemplo n.º 11
0
    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)
Exemplo n.º 12
0
    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))
Exemplo n.º 13
0
Arquivo: numbers.py Projeto: NO2/sympy
 def _eval_order(self, *symbols):
     # Order(5, x, y) -> Order(1,x,y)
     return C.Order(S.One, *symbols)