.. _polys-ringseries:

=====================================
Series Manipulation using Polynomials
=====================================

Any finite Taylor series, for all practical purposes is, in fact a polynomial.
This module makes use of the efficient representation and operations of sparse
polynomials for very fast multivariate series manipulations. Typical speedups
compared to SymPy's ``series`` method are in the range 20-100, with the gap
widening as the series being handled gets larger.

All the functions expand any given series on some ring specified by the user.
Thus, the coefficients of the calculated series depend on the ring being used.
For example::

    >>> from sympy.polys import ring, QQ, RR
    >>> from sympy.polys.ring_series import rs_sin
    >>> R, x, y = ring('x, y', QQ)
    >>> rs_sin(x*y, x, 5)
    -1/6*x**3*y**3 + x*y

``QQ`` stands for the Rational domain. Here all coefficients are rationals. It
is recommended to use ``QQ`` with ring series as it automatically chooses the
fastest Rational type.

Similarly, if a Real domain is used::

    >>> R, x, y = ring('x, y', RR)
    >>> rs_sin(x*y, x, 5)
    -0.166666666666667*x**3*y**3 + x*y

Though the definition of a polynomial limits the use of Polynomial module to
Taylor series, we extend it to allow Laurent and even Puiseux series (with
fractional exponents)::

    >>> from sympy.polys.ring_series import rs_cos, rs_tan
    >>> R, x, y = ring('x, y', QQ)

    >>> rs_cos(x + x*y, x, 3)/x**3
    -1/2*x**(-1)*y**2 - x**(-1)*y - 1/2*x**(-1) + x**(-3)

    >>> rs_tan(x**QQ(2, 5)*y**QQ(1, 2), x, 2)
    1/3*x**(6/5)*y**(3/2) + x**(2/5)*y**(1/2)

By default, ``PolyElement`` did not allow non-natural numbers as exponents. It
converted a fraction to an integer and raised an error on getting negative
exponents. The goal of the ``ring series`` module is fast series expansion, and
not to use the ``polys`` module. The reason we use it as our backend is simply
because it implements a sparse representation and most of the basic functions
that we need. However, this default behaviour of ``polys`` was limiting for
``ring series``.

Note that there is no such constraint (in having rational exponents) in the
data-structure used by ``polys``- ``dict``. Sparse polynomials
(``PolyElement``) use the Python dict to store a polynomial term by term, where
a tuple of exponents is the key and the coefficient of that term is the value.
There is no reason why we can't have rational values in the ``dict`` so as to
support rational exponents.

So the approach we took was to modify sparse ``polys`` to allow non-natural
exponents. And it turned out to be quite simple. We only had to delete the
conversion to ``int`` of exponents in the ``__pow__`` method of
``PolyElement``. So::

    >>> x**QQ(3, 4)
    x**(3/4)

and not ``1`` as was the case earlier.

Though this change violates the definition of a polynomial, it doesn't break
anything yet.  Ideally, we shouldn't modify ``polys`` in any way. But to have
all the ``series`` capabilities we want, no other simple way was found. If need
be, we can separate the modified part of ``polys`` from core ``polys``. It
would be great if any other elegant solution is found.

All series returned by the functions of this module are instances of the
``PolyElement`` class. To use them with other SymPy types, convert them  to
``Expr``::

    >>> from sympy.polys.ring_series import rs_exp
    >>> from sympy.abc import a, b, c
    >>> series = rs_exp(x, x, 5)
    >>> a + series.as_expr()
    a + x**4/24 + x**3/6 + x**2/2 + x + 1

rs_series
=========

Direct use of elementary ring series functions does give more control, but is
limiting at the same time. Creating an appropriate ring for the desired series
expansion and knowing which ring series function to call, are things not
everyone might be familiar with.

`rs\_series` is a function that takes an arbitrary ``Expr`` and returns its
expansion by calling the appropriate ring series functions. The returned series
is a polynomial over the simplest (almost) possible ring that does the job. It
recursively builds the ring as it parses the given expression, adding
generators to the ring when it needs them. Some examples::

    >>> from sympy.polys.ring_series import rs_series
    >>> from sympy.functions.elementary.trigonometric import sin
    >>> rs_series(sin(a + b), a, 5) # doctest: +SKIP
    1/24*sin(b)*a**4 - 1/2*sin(b)*a**2 + sin(b) - 1/6*cos(b)*a**3 + cos(b)*a

    >>> rs_series(sin(exp(a*b) + cos(a + c)), a, 2) # doctest: +SKIP
    -sin(c)*cos(cos(c) + 1)*a + cos(cos(c) + 1)*a*b + sin(cos(c) + 1)

    >>> rs_series(sin(a + b)*cos(a + c)*tan(a**2 + b), a, 2) # doctest: +SKIP
    cos(b)*cos(c)*tan(b)*a - sin(b)*sin(c)*tan(b)*a + sin(b)*cos(c)*tan(b)

It can expand complicated multivariate expressions involving multiple functions
and most importantly, it does so blazingly fast::

    >>> %timeit ((sin(a) + cos(a))**10).series(a, 0, 5) # doctest: +SKIP
    1 loops, best of 3: 1.33 s per loop

    >>> %timeit rs_series((sin(a) + cos(a))**10, a, 5) # doctest: +SKIP
    100 loops, best of 3: 4.13 ms per loop

`rs\_series` is over 300 times faster. Given an expression to expand, there is
some fixed overhead to parse it. Thus, for larger orders, the speed
improvement becomes more prominent::

    >>> %timeit rs_series((sin(a) + cos(a))**10, a, 100) # doctest: +SKIP
    10 loops, best of 3: 32.8 ms per loop

To figure out the right ring for a given expression, `rs\_series` uses the
``sring`` function, which in turn uses other functions of ``polys``. As
explained above, non-natural exponents are not allowed. But the restriction is
on exponents and not generators. So, ``polys`` allows all sorts of symbolic
terms as generators to make sure that the exponent is a natural number::

    >>> from sympy.polys.rings import sring
    >>> R, expr = sring(1/a**3 + a**QQ(3, 7)); R
    Polynomial ring in 1/a, a**(1/7) over ZZ with lex order

In the above example, `1/a` and `a**(1/7)` will be treated as completely
different atoms. For all practical purposes, we could let `b = 1/a` and `c =
a**(1/7)` and do the manipulations. Effectively, expressions involving `1/a`
and `a**(1/7)` (and their powers) will never simplify::

    >>> expr*R(1/a)
    (1/a)**4 + (1/a)*(a**(1/7))**3

This leads to similar issues with manipulating Laurent and Puiseux series as
faced earlier. Fortunately, this time we have an elegant solution and are able
to isolate the ``series`` and ``polys`` behaviour from one another. We
introduce a boolean flag ``series`` in the list of allowed ``Options`` for
polynomials (see :class:`sympy.polys.polyoptions.Options`). Thus, when we want
``sring`` to allow rational exponents we supply a ``series=True`` flag to
``sring``::

    >>> rs_series(sin(a**QQ(1, 3)), a, 3)
    -1/5040*a**(7/3) + 1/120*a**(5/3) - 1/6*a + a**(1/3)

Contribute
==========

`rs\_series` is not fully implemented yet. As of now, it supports only
multivariate Taylor expansions of expressions involving ``sin``, ``cos``,
``exp`` and ``tan``. Adding the remaining functions is not at all difficult and
they will be gradually added. If you are interested in helping, read the
comments in ``ring_series.py``. Currently, it does not support Puiseux series
(though the elementary functions do). This is expected to be fixed soon.

You can also add more functions to ``ring_series.py``. Only elementary
functions are supported currently. The long term goal is to replace SymPy's
current ``series`` method with ``rs_series``.

Reference
=========

.. currentmodule:: sympy.polys.ring_series

Functions in this module carry the prefix ``rs_``, standing for "ring series".
They manipulate finite power series in the sparse representation provided
by ``polys.ring.ring``.

**Elementary functions**

.. autofunction:: rs_log
.. autofunction:: rs_LambertW
.. autofunction:: rs_exp
.. autofunction:: rs_atan
.. autofunction:: rs_asin
.. autofunction:: rs_tan
.. autofunction:: _tan1
.. autofunction:: rs_cot
.. autofunction:: rs_sin
.. autofunction:: rs_cos
.. autofunction:: rs_cos_sin
.. autofunction:: rs_atanh
.. autofunction:: rs_sinh
.. autofunction:: rs_cosh
.. autofunction:: rs_tanh
.. autofunction:: rs_hadamard_exp

**Operations**

.. autofunction:: rs_mul
.. autofunction:: rs_square
.. autofunction:: rs_pow
.. autofunction:: rs_series_inversion
.. autofunction:: rs_series_reversion
.. autofunction:: rs_nth_root
.. autofunction:: rs_trunc
.. autofunction:: rs_subs
.. autofunction:: rs_diff
.. autofunction:: rs_integrate
.. autofunction:: rs_newton
.. autofunction:: rs_compose_add

**Utility functions**

.. autofunction:: rs_is_puiseux
.. autofunction:: rs_puiseux
.. autofunction:: rs_puiseux2
.. autofunction:: rs_series_from_list
.. autofunction:: rs_fun
.. autofunction:: mul_xin
.. autofunction:: pow_xin
