|
"""Power series evaluation and manipulation using sparse Polynomials |
|
|
|
Implementing a new function |
|
--------------------------- |
|
|
|
There are a few things to be kept in mind when adding a new function here:: |
|
|
|
- The implementation should work on all possible input domains/rings. |
|
Special cases include the ``EX`` ring and a constant term in the series |
|
to be expanded. There can be two types of constant terms in the series: |
|
|
|
+ A constant value or symbol. |
|
+ A term of a multivariate series not involving the generator, with |
|
respect to which the series is to expanded. |
|
|
|
Strictly speaking, a generator of a ring should not be considered a |
|
constant. However, for series expansion both the cases need similar |
|
treatment (as the user does not care about inner details), i.e, use an |
|
addition formula to separate the constant part and the variable part (see |
|
rs_sin for reference). |
|
|
|
- All the algorithms used here are primarily designed to work for Taylor |
|
series (number of iterations in the algo equals the required order). |
|
Hence, it becomes tricky to get the series of the right order if a |
|
Puiseux series is input. Use rs_puiseux? in your function if your |
|
algorithm is not designed to handle fractional powers. |
|
|
|
Extending rs_series |
|
------------------- |
|
|
|
To make a function work with rs_series you need to do two things:: |
|
|
|
- Many sure it works with a constant term (as explained above). |
|
- If the series contains constant terms, you might need to extend its ring. |
|
You do so by adding the new terms to the rings as generators. |
|
``PolyRing.compose`` and ``PolyRing.add_gens`` are two functions that do |
|
so and need to be called every time you expand a series containing a |
|
constant term. |
|
|
|
Look at rs_sin and rs_series for further reference. |
|
|
|
""" |
|
|
|
from sympy.polys.domains import QQ, EX |
|
from sympy.polys.rings import PolyElement, ring, sring |
|
from sympy.polys.puiseux import PuiseuxPoly |
|
from sympy.polys.polyerrors import DomainError |
|
from sympy.polys.monomials import (monomial_min, monomial_mul, monomial_div, |
|
monomial_ldiv) |
|
from mpmath.libmp.libintmath import ifac |
|
from sympy.core import PoleError, Function, Expr |
|
from sympy.core.numbers import Rational |
|
from sympy.core.intfunc import igcd |
|
from sympy.functions import (sin, cos, tan, atan, exp, atanh, asinh, tanh, log, |
|
ceiling, sinh, cosh) |
|
from sympy.utilities.misc import as_int |
|
from mpmath.libmp.libintmath import giant_steps |
|
import math |
|
|
|
|
|
def _invert_monoms(p1): |
|
""" |
|
Compute ``x**n * p1(1/x)`` for a univariate polynomial ``p1`` in ``x``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import ZZ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import _invert_monoms |
|
>>> R, x = ring('x', ZZ) |
|
>>> p = x**2 + 2*x + 3 |
|
>>> _invert_monoms(p) |
|
3*x**2 + 2*x + 1 |
|
|
|
See Also |
|
======== |
|
|
|
sympy.polys.densebasic.dup_reverse |
|
""" |
|
terms = list(p1.items()) |
|
terms.sort() |
|
deg = p1.degree() |
|
R = p1.ring |
|
p = R.zero |
|
cv = p1.listcoeffs() |
|
mv = p1.listmonoms() |
|
for mvi, cvi in zip(mv, cv): |
|
p[(deg - mvi[0],)] = cvi |
|
return p |
|
|
|
def _giant_steps(target): |
|
"""Return a list of precision steps for the Newton's method""" |
|
|
|
res = giant_steps(2, math.ceil(target)) |
|
if res[0] != 2: |
|
res = [2] + res |
|
return res |
|
|
|
def rs_trunc(p1, x, prec): |
|
""" |
|
Truncate the series in the ``x`` variable with precision ``prec``, |
|
that is, modulo ``O(x**prec)`` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_trunc |
|
>>> R, x = ring('x', QQ) |
|
>>> p = x**10 + x**5 + x + 1 |
|
>>> rs_trunc(p, x, 12) |
|
x**10 + x**5 + x + 1 |
|
>>> rs_trunc(p, x, 10) |
|
x**5 + x + 1 |
|
""" |
|
R = p1.ring |
|
p = {} |
|
i = R.gens.index(x) |
|
for exp1 in p1: |
|
if exp1[i] >= prec: |
|
continue |
|
p[exp1] = p1[exp1] |
|
return R(p) |
|
|
|
def rs_is_puiseux(p, x): |
|
""" |
|
Test if ``p`` is Puiseux series in ``x``. |
|
|
|
Raise an exception if it has a negative power in ``x``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> from sympy.polys.ring_series import rs_is_puiseux |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> p = x**QQ(2,5) + x**QQ(2,3) + x |
|
>>> rs_is_puiseux(p, x) |
|
True |
|
""" |
|
index = p.ring.gens.index(x) |
|
for k in p.itermonoms(): |
|
if k[index] != int(k[index]): |
|
return True |
|
if k[index] < 0: |
|
raise ValueError('The series is not regular in %s' % x) |
|
return False |
|
|
|
def rs_puiseux(f, p, x, prec): |
|
""" |
|
Return the puiseux series for `f(p, x, prec)`. |
|
|
|
To be used when function ``f`` is implemented only for regular series. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> from sympy.polys.ring_series import rs_puiseux, rs_exp |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> p = x**QQ(2,5) + x**QQ(2,3) + x |
|
>>> rs_puiseux(rs_exp,p, x, 1) |
|
1 + x**(2/5) + x**(2/3) + 1/2*x**(4/5) |
|
""" |
|
index = p.ring.gens.index(x) |
|
n = 1 |
|
for k in p: |
|
power = k[index] |
|
if isinstance(power, Rational): |
|
num, den = power.as_numer_denom() |
|
n = int(n*den // igcd(n, den)) |
|
elif power != int(power): |
|
den = power.denominator |
|
n = int(n*den // igcd(n, den)) |
|
if n != 1: |
|
p1 = pow_xin(p, index, n) |
|
r = f(p1, x, prec*n) |
|
n1 = QQ(1, n) |
|
if isinstance(r, tuple): |
|
r = tuple([pow_xin(rx, index, n1) for rx in r]) |
|
else: |
|
r = pow_xin(r, index, n1) |
|
else: |
|
r = f(p, x, prec) |
|
return r |
|
|
|
def rs_puiseux2(f, p, q, x, prec): |
|
""" |
|
Return the puiseux series for `f(p, q, x, prec)`. |
|
|
|
To be used when function ``f`` is implemented only for regular series. |
|
""" |
|
index = p.ring.gens.index(x) |
|
n = 1 |
|
for k in p: |
|
power = k[index] |
|
if isinstance(power, Rational): |
|
num, den = power.as_numer_denom() |
|
n = n*den // igcd(n, den) |
|
elif power != int(power): |
|
den = power.denominator |
|
n = n*den // igcd(n, den) |
|
if n != 1: |
|
p1 = pow_xin(p, index, n) |
|
r = f(p1, q, x, prec*n) |
|
n1 = QQ(1, n) |
|
r = pow_xin(r, index, n1) |
|
else: |
|
r = f(p, q, x, prec) |
|
return r |
|
|
|
def rs_mul(p1, p2, x, prec): |
|
""" |
|
Return the product of the given two series, modulo ``O(x**prec)``. |
|
|
|
``x`` is the series variable or its position in the generators. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_mul |
|
>>> R, x = ring('x', QQ) |
|
>>> p1 = x**2 + 2*x + 1 |
|
>>> p2 = x + 1 |
|
>>> rs_mul(p1, p2, x, 3) |
|
3*x**2 + 3*x + 1 |
|
""" |
|
R = p1.ring |
|
p = {} |
|
if R.__class__ != p2.ring.__class__ or R != p2.ring: |
|
raise ValueError('p1 and p2 must have the same ring') |
|
iv = R.gens.index(x) |
|
if not isinstance(p2, (PolyElement, PuiseuxPoly)): |
|
raise ValueError('p2 must be a polynomial') |
|
if R == p2.ring: |
|
get = p.get |
|
items2 = p2.terms() |
|
items2.sort(key=lambda e: e[0][iv]) |
|
if R.ngens == 1: |
|
for exp1, v1 in p1.iterterms(): |
|
for exp2, v2 in items2: |
|
exp = exp1[0] + exp2[0] |
|
if exp < prec: |
|
exp = (exp, ) |
|
p[exp] = get(exp, 0) + v1*v2 |
|
else: |
|
break |
|
else: |
|
monomial_mul = R.monomial_mul |
|
for exp1, v1 in p1.iterterms(): |
|
for exp2, v2 in items2: |
|
if exp1[iv] + exp2[iv] < prec: |
|
exp = monomial_mul(exp1, exp2) |
|
p[exp] = get(exp, 0) + v1*v2 |
|
else: |
|
break |
|
|
|
return R(p) |
|
|
|
def rs_square(p1, x, prec): |
|
""" |
|
Square the series modulo ``O(x**prec)`` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_square |
|
>>> R, x = ring('x', QQ) |
|
>>> p = x**2 + 2*x + 1 |
|
>>> rs_square(p, x, 3) |
|
6*x**2 + 4*x + 1 |
|
""" |
|
R = p1.ring |
|
p = {} |
|
iv = R.gens.index(x) |
|
get = p.get |
|
items = p1.terms() |
|
items.sort(key=lambda e: e[0][iv]) |
|
monomial_mul = R.monomial_mul |
|
for i in range(len(items)): |
|
exp1, v1 = items[i] |
|
for j in range(i): |
|
exp2, v2 = items[j] |
|
if exp1[iv] + exp2[iv] < prec: |
|
exp = monomial_mul(exp1, exp2) |
|
p[exp] = get(exp, 0) + v1*v2 |
|
else: |
|
break |
|
p = {m: 2*v for m, v in p.items()} |
|
get = p.get |
|
for expv, v in p1.iterterms(): |
|
if 2*expv[iv] < prec: |
|
e2 = monomial_mul(expv, expv) |
|
p[e2] = get(e2, 0) + v**2 |
|
return R(p) |
|
|
|
def rs_pow(p1, n, x, prec): |
|
""" |
|
Return ``p1**n`` modulo ``O(x**prec)`` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_pow |
|
>>> R, x = ring('x', QQ) |
|
>>> p = x + 1 |
|
>>> rs_pow(p, 4, x, 3) |
|
6*x**2 + 4*x + 1 |
|
""" |
|
R = p1.ring |
|
if isinstance(n, Rational): |
|
np = int(n.p) |
|
nq = int(n.q) |
|
if nq != 1: |
|
res = rs_nth_root(p1, nq, x, prec) |
|
if np != 1: |
|
res = rs_pow(res, np, x, prec) |
|
else: |
|
res = rs_pow(p1, np, x, prec) |
|
return res |
|
|
|
n = as_int(n) |
|
if n == 0: |
|
if p1: |
|
return R(1) |
|
else: |
|
raise ValueError('0**0 is undefined') |
|
if n < 0: |
|
p1 = rs_pow(p1, -n, x, prec) |
|
return rs_series_inversion(p1, x, prec) |
|
if n == 1: |
|
return rs_trunc(p1, x, prec) |
|
if n == 2: |
|
return rs_square(p1, x, prec) |
|
if n == 3: |
|
p2 = rs_square(p1, x, prec) |
|
return rs_mul(p1, p2, x, prec) |
|
p = R(1) |
|
while 1: |
|
if n & 1: |
|
p = rs_mul(p1, p, x, prec) |
|
n -= 1 |
|
if not n: |
|
break |
|
p1 = rs_square(p1, x, prec) |
|
n = n // 2 |
|
return p |
|
|
|
def rs_subs(p, rules, x, prec): |
|
""" |
|
Substitution with truncation according to the mapping in ``rules``. |
|
|
|
Return a series with precision ``prec`` in the generator ``x`` |
|
|
|
Note that substitutions are not done one after the other |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_subs |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> p = x**2 + y**2 |
|
>>> rs_subs(p, {x: x+ y, y: x+ 2*y}, x, 3) |
|
2*x**2 + 6*x*y + 5*y**2 |
|
>>> (x + y)**2 + (x + 2*y)**2 |
|
2*x**2 + 6*x*y + 5*y**2 |
|
|
|
which differs from |
|
|
|
>>> rs_subs(rs_subs(p, {x: x+ y}, x, 3), {y: x+ 2*y}, x, 3) |
|
5*x**2 + 12*x*y + 8*y**2 |
|
|
|
Parameters |
|
---------- |
|
p : :class:`~.PolyElement` Input series. |
|
rules : ``dict`` with substitution mappings. |
|
x : :class:`~.PolyElement` in which the series truncation is to be done. |
|
prec : :class:`~.Integer` order of the series after truncation. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_subs |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_subs(x**2+y**2, {y: (x+y)**2}, x, 3) |
|
6*x**2*y**2 + x**2 + 4*x*y**3 + y**4 |
|
""" |
|
R = p.ring |
|
ngens = R.ngens |
|
d = R(0) |
|
for i in range(ngens): |
|
d[(i, 1)] = R.gens[i] |
|
for var in rules: |
|
d[(R.index(var), 1)] = rules[var] |
|
p1 = R(0) |
|
p_keys = sorted(p.keys()) |
|
for expv in p_keys: |
|
p2 = R(1) |
|
for i in range(ngens): |
|
power = expv[i] |
|
if power == 0: |
|
continue |
|
if (i, power) not in d: |
|
q, r = divmod(power, 2) |
|
if r == 0 and (i, q) in d: |
|
d[(i, power)] = rs_square(d[(i, q)], x, prec) |
|
elif (i, power - 1) in d: |
|
d[(i, power)] = rs_mul(d[(i, power - 1)], d[(i, 1)], |
|
x, prec) |
|
else: |
|
d[(i, power)] = rs_pow(d[(i, 1)], power, x, prec) |
|
p2 = rs_mul(p2, d[(i, power)], x, prec) |
|
p1 += p2*p[expv] |
|
return p1 |
|
|
|
def _has_constant_term(p, x): |
|
""" |
|
Check if ``p`` has a constant term in ``x`` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import _has_constant_term |
|
>>> R, x = ring('x', QQ) |
|
>>> p = x**2 + x + 1 |
|
>>> _has_constant_term(p, x) |
|
True |
|
""" |
|
R = p.ring |
|
iv = R.gens.index(x) |
|
zm = R.zero_monom |
|
a = [0]*R.ngens |
|
a[iv] = 1 |
|
miv = tuple(a) |
|
return any(monomial_min(expv, miv) == zm for expv in p) |
|
|
|
def _get_constant_term(p, x): |
|
"""Return constant term in p with respect to x |
|
|
|
Note that it is not simply `p[R.zero_monom]` as there might be multiple |
|
generators in the ring R. We want the `x`-free term which can contain other |
|
generators. |
|
""" |
|
R = p.ring |
|
i = R.gens.index(x) |
|
zm = R.zero_monom |
|
a = [0]*R.ngens |
|
a[i] = 1 |
|
miv = tuple(a) |
|
c = 0 |
|
for expv in p: |
|
if monomial_min(expv, miv) == zm: |
|
c += R({expv: p[expv]}) |
|
return c |
|
|
|
def _check_series_var(p, x, name): |
|
index = p.ring.gens.index(x) |
|
m = min(p, key=lambda k: k[index])[index] |
|
if m < 0: |
|
raise PoleError("Asymptotic expansion of %s around [oo] not " |
|
"implemented." % name) |
|
return index, m |
|
|
|
def _series_inversion1(p, x, prec): |
|
""" |
|
Univariate series inversion ``1/p`` modulo ``O(x**prec)``. |
|
|
|
The Newton method is used. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import _series_inversion1 |
|
>>> R, x = ring('x', QQ) |
|
>>> p = x + 1 |
|
>>> _series_inversion1(p, x, 4) |
|
-x**3 + x**2 - x + 1 |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(_series_inversion1, p, x, prec) |
|
R = p.ring |
|
zm = R.zero_monom |
|
c = p[zm] |
|
|
|
|
|
|
|
if prec == int(prec): |
|
prec = int(prec) |
|
|
|
if zm not in p: |
|
raise ValueError("No constant term in series") |
|
if _has_constant_term(p - c, x): |
|
raise ValueError("p cannot contain a constant term depending on " |
|
"parameters") |
|
if not R.domain.is_unit(c): |
|
raise ValueError(f"Constant term {c} must be a unit in {R.domain}") |
|
|
|
one = R(1) |
|
if R.domain is EX: |
|
one = 1 |
|
if c != one: |
|
p1 = R(1)/c |
|
else: |
|
p1 = R(1) |
|
for precx in _giant_steps(prec): |
|
t = 1 - rs_mul(p1, p, x, precx) |
|
p1 = p1 + rs_mul(p1, t, x, precx) |
|
return p1 |
|
|
|
def rs_series_inversion(p, x, prec): |
|
""" |
|
Multivariate series inversion ``1/p`` modulo ``O(x**prec)``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_series_inversion |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_series_inversion(1 + x*y**2, x, 4) |
|
-x**3*y**6 + x**2*y**4 - x*y**2 + 1 |
|
>>> rs_series_inversion(1 + x*y**2, y, 4) |
|
-x*y**2 + 1 |
|
>>> rs_series_inversion(x + x**2, x, 4) |
|
x**3 - x**2 + x - 1 + x**(-1) |
|
""" |
|
R = p.ring |
|
if p == R.zero: |
|
raise ZeroDivisionError |
|
zm = R.zero_monom |
|
index = R.gens.index(x) |
|
m = min(p, key=lambda k: k[index])[index] |
|
if m: |
|
p = mul_xin(p, index, -m) |
|
prec = prec + m |
|
if zm not in p: |
|
raise NotImplementedError("No constant term in series") |
|
|
|
if _has_constant_term(p - p[zm], x): |
|
raise NotImplementedError("p - p[0] must not have a constant term in " |
|
"the series variables") |
|
r = _series_inversion1(p, x, prec) |
|
if m != 0: |
|
r = mul_xin(r, index, -m) |
|
return r |
|
|
|
def _coefficient_t(p, t): |
|
r"""Coefficient of `x_i**j` in p, where ``t`` = (i, j)""" |
|
i, j = t |
|
R = p.ring |
|
expv1 = [0]*R.ngens |
|
expv1[i] = j |
|
expv1 = tuple(expv1) |
|
p1 = R(0) |
|
for expv in p: |
|
if expv[i] == j: |
|
p1[monomial_div(expv, expv1)] = p[expv] |
|
return p1 |
|
|
|
def rs_series_reversion(p, x, n, y): |
|
r""" |
|
Reversion of a series. |
|
|
|
``p`` is a series with ``O(x**n)`` of the form $p = ax + f(x)$ |
|
where $a$ is a number different from 0. |
|
|
|
$f(x) = \sum_{k=2}^{n-1} a_kx_k$ |
|
|
|
Parameters |
|
========== |
|
|
|
a_k : Can depend polynomially on other variables, not indicated. |
|
x : Variable with name x. |
|
y : Variable with name y. |
|
|
|
Returns |
|
======= |
|
|
|
Solve $p = y$, that is, given $ax + f(x) - y = 0$, |
|
find the solution $x = r(y)$ up to $O(y^n)$. |
|
|
|
Algorithm |
|
========= |
|
|
|
If $r_i$ is the solution at order $i$, then: |
|
$ar_i + f(r_i) - y = O\left(y^{i + 1}\right)$ |
|
|
|
and if $r_{i + 1}$ is the solution at order $i + 1$, then: |
|
$ar_{i + 1} + f(r_{i + 1}) - y = O\left(y^{i + 2}\right)$ |
|
|
|
We have, $r_{i + 1} = r_i + e$, such that, |
|
$ae + f(r_i) = O\left(y^{i + 2}\right)$ |
|
or $e = -f(r_i)/a$ |
|
|
|
So we use the recursion relation: |
|
$r_{i + 1} = r_i - f(r_i)/a$ |
|
with the boundary condition: $r_1 = y$ |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_series_reversion, rs_trunc |
|
>>> R, x, y, a, b = ring('x, y, a, b', QQ) |
|
>>> p = x - x**2 - 2*b*x**2 + 2*a*b*x**2 |
|
>>> p1 = rs_series_reversion(p, x, 3, y); p1 |
|
-2*y**2*a*b + 2*y**2*b + y**2 + y |
|
>>> rs_trunc(p.compose(x, p1), y, 3) |
|
y |
|
""" |
|
if rs_is_puiseux(p, x): |
|
raise NotImplementedError |
|
R = p.ring |
|
nx = R.gens.index(x) |
|
y = R(y) |
|
ny = R.gens.index(y) |
|
if _has_constant_term(p, x): |
|
raise ValueError("p must not contain a constant term in the series " |
|
"variable") |
|
a = _coefficient_t(p, (nx, 1)) |
|
zm = R.zero_monom |
|
assert zm in a and len(a) == 1 |
|
a = a[zm] |
|
r = y/a |
|
for i in range(2, n): |
|
sp = rs_subs(p, {x: r}, y, i + 1) |
|
sp = _coefficient_t(sp, (ny, i))*y**i |
|
r -= sp/a |
|
return r |
|
|
|
def rs_series_from_list(p, c, x, prec, concur=1): |
|
""" |
|
Return a series `sum c[n]*p**n` modulo `O(x**prec)`. |
|
|
|
It reduces the number of multiplications by summing concurrently. |
|
|
|
`ax = [1, p, p**2, .., p**(J - 1)]` |
|
`s = sum(c[i]*ax[i]` for i in `range(r, (r + 1)*J))*p**((K - 1)*J)` |
|
with `K >= (n + 1)/J` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_series_from_list, rs_trunc |
|
>>> R, x = ring('x', QQ) |
|
>>> p = x**2 + x + 1 |
|
>>> c = [1, 2, 3] |
|
>>> rs_series_from_list(p, c, x, 4) |
|
6*x**3 + 11*x**2 + 8*x + 6 |
|
>>> rs_trunc(1 + 2*p + 3*p**2, x, 4) |
|
6*x**3 + 11*x**2 + 8*x + 6 |
|
>>> pc = R.from_list(list(reversed(c))) |
|
>>> rs_trunc(pc.compose(x, p), x, 4) |
|
6*x**3 + 11*x**2 + 8*x + 6 |
|
|
|
See Also |
|
======== |
|
|
|
sympy.polys.rings.PolyRing.compose |
|
|
|
""" |
|
R = p.ring |
|
n = len(c) |
|
if not concur: |
|
q = R(1) |
|
s = c[0]*q |
|
for i in range(1, n): |
|
q = rs_mul(q, p, x, prec) |
|
s += c[i]*q |
|
return s |
|
J = int(math.sqrt(n) + 1) |
|
K, r = divmod(n, J) |
|
if r: |
|
K += 1 |
|
ax = [R(1)] |
|
q = R(1) |
|
if len(p) < 20: |
|
for i in range(1, J): |
|
q = rs_mul(q, p, x, prec) |
|
ax.append(q) |
|
else: |
|
for i in range(1, J): |
|
if i % 2 == 0: |
|
q = rs_square(ax[i//2], x, prec) |
|
else: |
|
q = rs_mul(q, p, x, prec) |
|
ax.append(q) |
|
|
|
pj = rs_mul(ax[-1], p, x, prec) |
|
b = R(1) |
|
s = R(0) |
|
for k in range(K - 1): |
|
r = J*k |
|
s1 = c[r] |
|
for j in range(1, J): |
|
s1 += c[r + j]*ax[j] |
|
s1 = rs_mul(s1, b, x, prec) |
|
s += s1 |
|
b = rs_mul(b, pj, x, prec) |
|
if not b: |
|
break |
|
k = K - 1 |
|
r = J*k |
|
if r < n: |
|
s1 = c[r]*R(1) |
|
for j in range(1, J): |
|
if r + j >= n: |
|
break |
|
s1 += c[r + j]*ax[j] |
|
s1 = rs_mul(s1, b, x, prec) |
|
s += s1 |
|
return s |
|
|
|
def rs_diff(p, x): |
|
""" |
|
Return partial derivative of ``p`` with respect to ``x``. |
|
|
|
Parameters |
|
========== |
|
|
|
x : :class:`~.PolyElement` with respect to which ``p`` is differentiated. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_diff |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> p = x + x**2*y**3 |
|
>>> rs_diff(p, x) |
|
2*x*y**3 + 1 |
|
""" |
|
R = p.ring |
|
n = R.gens.index(x) |
|
p1 = {} |
|
mn = [0]*R.ngens |
|
mn[n] = 1 |
|
mn = tuple(mn) |
|
for expv in p: |
|
if expv[n]: |
|
e = monomial_ldiv(expv, mn) |
|
p1[e] = R.domain_new(p[expv]*expv[n]) |
|
return R(p1) |
|
|
|
def rs_integrate(p, x): |
|
""" |
|
Integrate ``p`` with respect to ``x``. |
|
|
|
Parameters |
|
========== |
|
|
|
x : :class:`~.PolyElement` with respect to which ``p`` is integrated. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_integrate |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> p = x + x**2*y**3 |
|
>>> rs_integrate(p, x) |
|
1/3*x**3*y**3 + 1/2*x**2 |
|
""" |
|
R = p.ring |
|
p1 = {} |
|
n = R.gens.index(x) |
|
mn = [0]*R.ngens |
|
mn[n] = 1 |
|
mn = tuple(mn) |
|
|
|
for expv in p: |
|
e = monomial_mul(expv, mn) |
|
p1[e] = R.domain_new(p[expv]/(expv[n] + 1)) |
|
return R(p1) |
|
|
|
def rs_fun(p, f, *args): |
|
r""" |
|
Function of a multivariate series computed by substitution. |
|
|
|
The case with f method name is used to compute `rs\_tan` and `rs\_nth\_root` |
|
of a multivariate series: |
|
|
|
`rs\_fun(p, tan, iv, prec)` |
|
|
|
tan series is first computed for a dummy variable _x, |
|
i.e, `rs\_tan(\_x, iv, prec)`. Then we substitute _x with p to get the |
|
desired series |
|
|
|
Parameters |
|
========== |
|
|
|
p : :class:`~.PolyElement` The multivariate series to be expanded. |
|
f : `ring\_series` function to be applied on `p`. |
|
args[-2] : :class:`~.PolyElement` with respect to which, the series is to be expanded. |
|
args[-1] : Required order of the expanded series. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_fun, _tan1 |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> p = x + x*y + x**2*y + x**3*y**2 |
|
>>> rs_fun(p, _tan1, x, 4) |
|
1/3*x**3*y**3 + 2*x**3*y**2 + x**3*y + 1/3*x**3 + x**2*y + x*y + x |
|
""" |
|
_R = p.ring |
|
R1, _x = ring('_x', _R.domain) |
|
h = int(args[-1]) |
|
args1 = args[:-2] + (_x, h) |
|
zm = _R.zero_monom |
|
|
|
|
|
if zm in p: |
|
x1 = _x + p[zm] |
|
p1 = p - p[zm] |
|
else: |
|
x1 = _x |
|
p1 = p |
|
if isinstance(f, str): |
|
q = getattr(x1, f)(*args1) |
|
else: |
|
q = f(x1, *args1) |
|
a = sorted(q.items()) |
|
c = [0]*h |
|
for x in a: |
|
c[x[0][0]] = x[1] |
|
p1 = rs_series_from_list(p1, c, args[-2], args[-1]) |
|
return p1 |
|
|
|
def mul_xin(p, i, n): |
|
r""" |
|
Return `p*x_i**n`. |
|
|
|
`x\_i` is the ith variable in ``p``. |
|
""" |
|
R = p.ring |
|
q = {} |
|
for k, v in p.terms(): |
|
k1 = list(k) |
|
k1[i] += n |
|
q[tuple(k1)] = v |
|
return R(q) |
|
|
|
def pow_xin(p, i, n): |
|
""" |
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> from sympy.polys.ring_series import pow_xin |
|
>>> R, x, y = puiseux_ring('x, y', QQ) |
|
>>> p = x**QQ(2,5) + x + x**QQ(2,3) |
|
>>> index = p.ring.gens.index(x) |
|
>>> pow_xin(p, index, 15) |
|
x**6 + x**10 + x**15 |
|
""" |
|
R = p.ring |
|
q = {} |
|
for k, v in p.terms(): |
|
k1 = list(k) |
|
k1[i] *= n |
|
q[tuple(k1)] = v |
|
return R(q) |
|
|
|
def _nth_root1(p, n, x, prec): |
|
""" |
|
Univariate series expansion of the nth root of ``p``. |
|
|
|
The Newton method is used. |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux2(_nth_root1, p, n, x, prec) |
|
R = p.ring |
|
zm = R.zero_monom |
|
if zm not in p: |
|
raise NotImplementedError('No constant term in series') |
|
n = as_int(n) |
|
assert p[zm] == 1 |
|
p1 = R(1) |
|
if p == 1: |
|
return p |
|
if n == 0: |
|
return R(1) |
|
if n == 1: |
|
return p |
|
if n < 0: |
|
n = -n |
|
sign = 1 |
|
else: |
|
sign = 0 |
|
for precx in _giant_steps(prec): |
|
tmp = rs_pow(p1, n + 1, x, precx) |
|
tmp = rs_mul(tmp, p, x, precx) |
|
p1 += p1/n - tmp/n |
|
if sign: |
|
return p1 |
|
else: |
|
return _series_inversion1(p1, x, prec) |
|
|
|
def rs_nth_root(p, n, x, prec): |
|
""" |
|
Multivariate series expansion of the nth root of ``p``. |
|
|
|
Parameters |
|
========== |
|
|
|
p : Expr |
|
The polynomial to computer the root of. |
|
n : integer |
|
The order of the root to be computed. |
|
x : :class:`~.PolyElement` |
|
prec : integer |
|
Order of the expanded series. |
|
|
|
Notes |
|
===== |
|
|
|
The result of this function is dependent on the ring over which the |
|
polynomial has been defined. If the answer involves a root of a constant, |
|
make sure that the polynomial is over a real field. It cannot yet handle |
|
roots of symbols. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ, RR |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_nth_root |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_nth_root(1 + x + x*y, -3, x, 3) |
|
2/9*x**2*y**2 + 4/9*x**2*y + 2/9*x**2 - 1/3*x*y - 1/3*x + 1 |
|
>>> R, x, y = ring('x, y', RR) |
|
>>> rs_nth_root(3 + x + x*y, 3, x, 2) |
|
0.160249952256379*x*y + 0.160249952256379*x + 1.44224957030741 |
|
""" |
|
if n == 0: |
|
if p == 0: |
|
raise ValueError('0**0 expression') |
|
else: |
|
return p.ring(1) |
|
if n == 1: |
|
return rs_trunc(p, x, prec) |
|
R = p.ring |
|
index = R.gens.index(x) |
|
m = min(p, key=lambda k: k[index])[index] |
|
p = mul_xin(p, index, -m) |
|
prec -= m |
|
|
|
if _has_constant_term(p - 1, x): |
|
zm = R.zero_monom |
|
c = p[zm] |
|
if isinstance(c, PolyElement): |
|
try: |
|
c_expr = c.as_expr() |
|
const = R(c_expr**(QQ(1, n))) |
|
except ValueError: |
|
raise DomainError("The given series cannot be expanded in " |
|
"this domain.") |
|
else: |
|
try: |
|
const = R(c**Rational(1, n)) |
|
except ValueError: |
|
raise DomainError("The given series cannot be expanded in " |
|
"this domain.") |
|
res = rs_nth_root(p/c, n, x, prec)*const |
|
else: |
|
res = _nth_root1(p, n, x, prec) |
|
if m: |
|
m = QQ(m) / n |
|
res = mul_xin(res, index, m) |
|
return res |
|
|
|
def rs_log(p, x, prec): |
|
""" |
|
The Logarithm of ``p`` modulo ``O(x**prec)``. |
|
|
|
Notes |
|
===== |
|
|
|
Truncation of ``integral dx p**-1*d p/dx`` is used. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> from sympy.polys.ring_series import rs_log |
|
>>> R, x = puiseux_ring('x', QQ) |
|
>>> rs_log(1 + x, x, 8) |
|
x + -1/2*x**2 + 1/3*x**3 + -1/4*x**4 + 1/5*x**5 + -1/6*x**6 + 1/7*x**7 |
|
>>> rs_log(x**QQ(3, 2) + 1, x, 5) |
|
x**(3/2) + -1/2*x**3 + 1/3*x**(9/2) |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_log, p, x, prec) |
|
R = p.ring |
|
if p == 1: |
|
return R.zero |
|
c = _get_constant_term(p, x) |
|
if c: |
|
const = 0 |
|
if c == 1: |
|
pass |
|
try: |
|
c_expr = c.as_expr() |
|
const = R(log(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([log(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
const = R(log(c_expr)) |
|
|
|
dlog = p.diff(x) |
|
dlog = rs_mul(dlog, _series_inversion1(p, x, prec), x, prec - 1) |
|
return rs_integrate(dlog, x) + const |
|
else: |
|
raise NotImplementedError |
|
|
|
def rs_LambertW(p, x, prec): |
|
""" |
|
Calculate the series expansion of the principal branch of the Lambert W |
|
function. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_LambertW |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_LambertW(x + x*y, x, 3) |
|
-x**2*y**2 - 2*x**2*y - x**2 + x*y + x |
|
|
|
See Also |
|
======== |
|
|
|
LambertW |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_LambertW, p, x, prec) |
|
R = p.ring |
|
p1 = R(0) |
|
if _has_constant_term(p, x): |
|
raise NotImplementedError("Polynomial must not have constant term in " |
|
"the series variables") |
|
if x in R.gens: |
|
for precx in _giant_steps(prec): |
|
e = rs_exp(p1, x, precx) |
|
p2 = rs_mul(e, p1, x, precx) - p |
|
p3 = rs_mul(e, p1 + 1, x, precx) |
|
p3 = rs_series_inversion(p3, x, precx) |
|
tmp = rs_mul(p2, p3, x, precx) |
|
p1 -= tmp |
|
return p1 |
|
else: |
|
raise NotImplementedError |
|
|
|
def _exp1(p, x, prec): |
|
r"""Helper function for `rs\_exp`. """ |
|
R = p.ring |
|
p1 = R(1) |
|
for precx in _giant_steps(prec): |
|
pt = p - rs_log(p1, x, precx) |
|
tmp = rs_mul(pt, p1, x, precx) |
|
p1 += tmp |
|
return p1 |
|
|
|
def rs_exp(p, x, prec): |
|
""" |
|
Exponentiation of a series modulo ``O(x**prec)`` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_exp |
|
>>> R, x = ring('x', QQ) |
|
>>> rs_exp(x**2, x, 7) |
|
1/6*x**6 + 1/2*x**4 + x**2 + 1 |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_exp, p, x, prec) |
|
R = p.ring |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
const = R(exp(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([exp(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
const = R(exp(c_expr)) |
|
|
|
p1 = p - c |
|
|
|
|
|
|
|
return const*rs_exp(p1, x, prec) |
|
|
|
if len(p) > 20: |
|
return _exp1(p, x, prec) |
|
one = R(1) |
|
n = 1 |
|
c = [] |
|
for k in range(prec): |
|
c.append(one/n) |
|
k += 1 |
|
n *= k |
|
|
|
r = rs_series_from_list(p, c, x, prec) |
|
return r |
|
|
|
def _atan(p, iv, prec): |
|
""" |
|
Expansion using formula. |
|
|
|
Faster on very small and univariate series. |
|
""" |
|
R = p.ring |
|
mo = R(-1) |
|
c = [-mo] |
|
p2 = rs_square(p, iv, prec) |
|
for k in range(1, prec): |
|
c.append(mo**k/(2*k + 1)) |
|
s = rs_series_from_list(p2, c, iv, prec) |
|
s = rs_mul(s, p, iv, prec) |
|
return s |
|
|
|
def rs_atan(p, x, prec): |
|
""" |
|
The arctangent of a series |
|
|
|
Return the series expansion of the atan of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_atan |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_atan(x + x*y, x, 4) |
|
-1/3*x**3*y**3 - x**3*y**2 - x**3*y - 1/3*x**3 + x*y + x |
|
|
|
See Also |
|
======== |
|
|
|
atan |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_atan, p, x, prec) |
|
R = p.ring |
|
const = 0 |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
const = R(atan(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([atan(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
const = R(atan(c_expr)) |
|
|
|
|
|
|
|
|
|
dp = p.diff(x) |
|
p1 = rs_square(p, x, prec) + R(1) |
|
p1 = rs_series_inversion(p1, x, prec - 1) |
|
p1 = rs_mul(dp, p1, x, prec - 1) |
|
return rs_integrate(p1, x) + const |
|
|
|
def rs_asin(p, x, prec): |
|
""" |
|
Arcsine of a series |
|
|
|
Return the series expansion of the asin of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_asin |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_asin(x, x, 8) |
|
5/112*x**7 + 3/40*x**5 + 1/6*x**3 + x |
|
|
|
See Also |
|
======== |
|
|
|
asin |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_asin, p, x, prec) |
|
if _has_constant_term(p, x): |
|
raise NotImplementedError("Polynomial must not have constant term in " |
|
"series variables") |
|
R = p.ring |
|
if x in R.gens: |
|
|
|
if len(p) > 20: |
|
dp = rs_diff(p, x) |
|
p1 = 1 - rs_square(p, x, prec - 1) |
|
p1 = rs_nth_root(p1, -2, x, prec - 1) |
|
p1 = rs_mul(dp, p1, x, prec - 1) |
|
return rs_integrate(p1, x) |
|
one = R(1) |
|
c = [0, one, 0] |
|
for k in range(3, prec, 2): |
|
c.append((k - 2)**2*c[-2]/(k*(k - 1))) |
|
c.append(0) |
|
return rs_series_from_list(p, c, x, prec) |
|
|
|
else: |
|
raise NotImplementedError |
|
|
|
def _tan1(p, x, prec): |
|
r""" |
|
Helper function of :func:`rs_tan`. |
|
|
|
Return the series expansion of tan of a univariate series using Newton's |
|
method. It takes advantage of the fact that series expansion of atan is |
|
easier than that of tan. |
|
|
|
Consider `f(x) = y - \arctan(x)` |
|
Let r be a root of f(x) found using Newton's method. |
|
Then `f(r) = 0` |
|
Or `y = \arctan(x)` where `x = \tan(y)` as required. |
|
""" |
|
R = p.ring |
|
p1 = R(0) |
|
for precx in _giant_steps(prec): |
|
tmp = p - rs_atan(p1, x, precx) |
|
tmp = rs_mul(tmp, 1 + rs_square(p1, x, precx), x, precx) |
|
p1 += tmp |
|
return p1 |
|
|
|
def rs_tan(p, x, prec): |
|
""" |
|
Tangent of a series. |
|
|
|
Return the series expansion of the tan of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_tan |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_tan(x + x*y, x, 4) |
|
1/3*x**3*y**3 + x**3*y**2 + x**3*y + 1/3*x**3 + x*y + x |
|
|
|
See Also |
|
======== |
|
|
|
_tan1, tan |
|
""" |
|
if rs_is_puiseux(p, x): |
|
r = rs_puiseux(rs_tan, p, x, prec) |
|
return r |
|
R = p.ring |
|
const = 0 |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
const = R(tan(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([tan(c_expr, )]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
const = R(tan(c_expr)) |
|
|
|
p1 = p - c |
|
|
|
|
|
|
|
t2 = rs_tan(p1, x, prec) |
|
t = rs_series_inversion(1 - const*t2, x, prec) |
|
return rs_mul(const + t2, t, x, prec) |
|
|
|
if R.ngens == 1: |
|
return _tan1(p, x, prec) |
|
else: |
|
return rs_fun(p, rs_tan, x, prec) |
|
|
|
def rs_cot(p, x, prec): |
|
""" |
|
Cotangent of a series |
|
|
|
Return the series expansion of the cot of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_cot |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_cot(x, x, 6) |
|
-2/945*x**5 - 1/45*x**3 - 1/3*x + x**(-1) |
|
|
|
See Also |
|
======== |
|
|
|
cot |
|
""" |
|
|
|
|
|
if rs_is_puiseux(p, x): |
|
r = rs_puiseux(rs_cot, p, x, prec) |
|
return r |
|
i, m = _check_series_var(p, x, 'cot') |
|
prec1 = int(prec + 2*m) |
|
c, s = rs_cos_sin(p, x, prec1) |
|
s = mul_xin(s, i, -m) |
|
s = rs_series_inversion(s, x, prec1) |
|
res = rs_mul(c, s, x, prec1) |
|
res = mul_xin(res, i, -m) |
|
res = rs_trunc(res, x, prec) |
|
return res |
|
|
|
def rs_sin(p, x, prec): |
|
""" |
|
Sine of a series |
|
|
|
Return the series expansion of the sin of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> from sympy.polys.ring_series import rs_sin |
|
>>> R, x, y = puiseux_ring('x, y', QQ) |
|
>>> rs_sin(x + x*y, x, 4) |
|
x + x*y + -1/6*x**3 + -1/2*x**3*y + -1/2*x**3*y**2 + -1/6*x**3*y**3 |
|
>>> rs_sin(x**QQ(3, 2) + x*y**QQ(7, 5), x, 4) |
|
x*y**(7/5) + x**(3/2) + -1/6*x**3*y**(21/5) + -1/2*x**(7/2)*y**(14/5) |
|
|
|
See Also |
|
======== |
|
|
|
sin |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_sin, p, x, prec) |
|
R = x.ring |
|
if not p: |
|
return R(0) |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
t1, t2 = R(sin(c_expr)), R(cos(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([sin(c_expr), cos(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
t1, t2 = R(sin(c_expr)), R(cos(c_expr)) |
|
|
|
p1 = p - c |
|
|
|
|
|
|
|
p_cos, p_sin = rs_cos_sin(p1, x, prec) |
|
return p_sin*t2 + p_cos*t1 |
|
|
|
|
|
if len(p) > 20 and R.ngens == 1: |
|
t = rs_tan(p/2, x, prec) |
|
t2 = rs_square(t, x, prec) |
|
p1 = rs_series_inversion(1 + t2, x, prec) |
|
return rs_mul(p1, 2*t, x, prec) |
|
one = R(1) |
|
n = 1 |
|
c = [0] |
|
for k in range(2, prec + 2, 2): |
|
c.append(one/n) |
|
c.append(0) |
|
n *= -k*(k + 1) |
|
return rs_series_from_list(p, c, x, prec) |
|
|
|
def rs_cos(p, x, prec): |
|
""" |
|
Cosine of a series |
|
|
|
Return the series expansion of the cos of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.puiseux import puiseux_ring |
|
>>> from sympy.polys.ring_series import rs_cos |
|
>>> R, x, y = puiseux_ring('x, y', QQ) |
|
>>> rs_cos(x + x*y, x, 4) |
|
1 + -1/2*x**2 + -1*x**2*y + -1/2*x**2*y**2 |
|
>>> rs_cos(x + x*y, x, 4)/x**QQ(7, 5) |
|
x**(-7/5) + -1/2*x**(3/5) + -1*x**(3/5)*y + -1/2*x**(3/5)*y**2 |
|
|
|
See Also |
|
======== |
|
|
|
cos |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_cos, p, x, prec) |
|
R = p.ring |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
t1, t2 = R(sin(c_expr)), R(cos(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([sin(c_expr), cos(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
t1, t2 = R(sin(c_expr)), R(cos(c_expr)) |
|
|
|
p1 = p - c |
|
|
|
|
|
p_cos, p_sin = rs_cos_sin(p1, x, prec) |
|
return p_cos*t2 - p_sin*t1 |
|
|
|
|
|
if len(p) > 20 and R.ngens == 1: |
|
t = rs_tan(p/2, x, prec) |
|
t2 = rs_square(t, x, prec) |
|
p1 = rs_series_inversion(1+t2, x, prec) |
|
return rs_mul(p1, 1 - t2, x, prec) |
|
one = R(1) |
|
n = 1 |
|
c = [] |
|
for k in range(2, prec + 2, 2): |
|
c.append(one/n) |
|
c.append(0) |
|
n *= -k*(k - 1) |
|
return rs_series_from_list(p, c, x, prec) |
|
|
|
def rs_cos_sin(p, x, prec): |
|
""" |
|
Cosine and sine of a series |
|
|
|
Return the series expansion of the cosine and sine of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_cos_sin |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> c, s = rs_cos_sin(x + x*y, x, 4) |
|
>>> c |
|
-1/2*x**2*y**2 - x**2*y - 1/2*x**2 + 1 |
|
>>> s |
|
-1/6*x**3*y**3 - 1/2*x**3*y**2 - 1/2*x**3*y - 1/6*x**3 + x*y + x |
|
|
|
See Also |
|
======== |
|
|
|
rs_cos, rs_sin |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_cos_sin, p, x, prec) |
|
R = p.ring |
|
if not p: |
|
return R(0), R(0) |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
t1, t2 = R(sin(c_expr)), R(cos(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([sin(c_expr), cos(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
t1, t2 = R(sin(c_expr)), R(cos(c_expr)) |
|
|
|
p1 = p - c |
|
p_cos, p_sin = rs_cos_sin(p1, x, prec) |
|
return p_cos*t2 - p_sin*t1, p_cos*t1 + p_sin*t2 |
|
|
|
if len(p) > 20 and R.ngens == 1: |
|
t = rs_tan(p/2, x, prec) |
|
t2 = rs_square(t, x, prec) |
|
p1 = rs_series_inversion(1 + t2, x, prec) |
|
return (rs_mul(p1, 1 - t2, x, prec), rs_mul(p1, 2*t, x, prec)) |
|
|
|
one = R(1) |
|
coeffs = [] |
|
cn, sn = 1, 1 |
|
for k in range(2, prec+2, 2): |
|
coeffs.extend([(one/cn, 0), (0, one/sn)]) |
|
cn, sn = -cn*k*(k - 1), -sn*k*(k + 1) |
|
|
|
c, s = zip(*coeffs) |
|
return (rs_series_from_list(p, c, x, prec), rs_series_from_list(p, s, x, prec)) |
|
|
|
def _atanh(p, x, prec): |
|
""" |
|
Expansion using formula |
|
|
|
Faster for very small and univariate series |
|
""" |
|
R = p.ring |
|
one = R(1) |
|
c = [one] |
|
p2 = rs_square(p, x, prec) |
|
for k in range(1, prec): |
|
c.append(one/(2*k + 1)) |
|
s = rs_series_from_list(p2, c, x, prec) |
|
s = rs_mul(s, p, x, prec) |
|
return s |
|
|
|
def rs_atanh(p, x, prec): |
|
""" |
|
Hyperbolic arctangent of a series |
|
|
|
Return the series expansion of the atanh of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_atanh |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_atanh(x + x*y, x, 4) |
|
1/3*x**3*y**3 + x**3*y**2 + x**3*y + 1/3*x**3 + x*y + x |
|
|
|
See Also |
|
======== |
|
|
|
atanh |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_atanh, p, x, prec) |
|
R = p.ring |
|
const = 0 |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
const = R(atanh(c_expr)) |
|
except ValueError: |
|
raise DomainError("The given series cannot be expanded in " |
|
"this domain.") |
|
|
|
|
|
|
|
|
|
dp = rs_diff(p, x) |
|
p1 = - rs_square(p, x, prec) + 1 |
|
p1 = rs_series_inversion(p1, x, prec - 1) |
|
p1 = rs_mul(dp, p1, x, prec - 1) |
|
return rs_integrate(p1, x) + const |
|
|
|
def rs_asinh(p, x, prec): |
|
""" |
|
Hyperbolic arcsine of a series |
|
|
|
Return the series expansion of the arcsinh of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_asinh |
|
>>> R, x = ring('x', QQ) |
|
>>> rs_asinh(x, x, 9) |
|
-5/112*x**7 + 3/40*x**5 - 1/6*x**3 + x |
|
|
|
See Also |
|
======== |
|
|
|
asinh |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_asinh, p, x, prec) |
|
R = p.ring |
|
const = 0 |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
const = R(asinh(c_expr)) |
|
except ValueError: |
|
raise DomainError("The given series cannot be expanded in " |
|
"this domain.") |
|
|
|
|
|
|
|
|
|
dp = rs_diff(p, x) |
|
p_squared = rs_square(p, x, prec) |
|
denom = p_squared + R(1) |
|
p1 = rs_nth_root(denom, -2, x, prec - 1) |
|
p1 = rs_mul(dp, p1, x, prec - 1) |
|
return rs_integrate(p1, x) + const |
|
|
|
def rs_sinh(p, x, prec): |
|
""" |
|
Hyperbolic sine of a series |
|
|
|
Return the series expansion of the sinh of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_sinh |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_sinh(x + x*y, x, 4) |
|
1/6*x**3*y**3 + 1/2*x**3*y**2 + 1/2*x**3*y + 1/6*x**3 + x*y + x |
|
|
|
See Also |
|
======== |
|
|
|
sinh |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_sinh, p, x, prec) |
|
R = p.ring |
|
if not p: |
|
return R(0) |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
t1, t2 = R(sinh(c_expr)), R(cosh(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([sinh(c_expr), cosh(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
t1, t2 = R(sinh(c_expr)), R(cosh(c_expr)) |
|
|
|
p1 = p - c |
|
p_cosh, p_sinh = rs_cosh_sinh(p1, x, prec) |
|
return p_sinh * t2 + p_cosh * t1 |
|
|
|
t = rs_exp(p, x, prec) |
|
t1 = rs_series_inversion(t, x, prec) |
|
return (t - t1)/2 |
|
|
|
def rs_cosh(p, x, prec): |
|
""" |
|
Hyperbolic cosine of a series |
|
|
|
Return the series expansion of the cosh of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_cosh |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_cosh(x + x*y, x, 4) |
|
1/2*x**2*y**2 + x**2*y + 1/2*x**2 + 1 |
|
|
|
See Also |
|
======== |
|
|
|
cosh |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_cosh, p, x, prec) |
|
R = p.ring |
|
if not p: |
|
return R(0) |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
t1, t2 = R(sinh(c_expr)), R(cosh(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([sinh(c_expr), cosh(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
t1, t2 = R(sinh(c_expr)), R(cosh(c_expr)) |
|
|
|
p1 = p - c |
|
p_cosh, p_sinh = rs_cosh_sinh(p1, x, prec) |
|
return p_cosh * t2 + p_sinh * t1 |
|
|
|
t = rs_exp(p, x, prec) |
|
t1 = rs_series_inversion(t, x, prec) |
|
return (t + t1)/2 |
|
|
|
def rs_cosh_sinh(p, x, prec): |
|
""" |
|
Hyperbolic cosine and sine of a series |
|
|
|
Return the series expansion of the hyperbolic cosine and sine of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_cosh_sinh |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> c, s = rs_cosh_sinh(x + x*y, x, 4) |
|
>>> c |
|
1/2*x**2*y**2 + x**2*y + 1/2*x**2 + 1 |
|
>>> s |
|
1/6*x**3*y**3 + 1/2*x**3*y**2 + 1/2*x**3*y + 1/6*x**3 + x*y + x |
|
|
|
See Also |
|
======== |
|
|
|
rs_cosh, rs_sinh |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_cosh_sinh, p, x, prec) |
|
R = p.ring |
|
if not p: |
|
return R(0), R(0) |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
t1, t2 = R(sinh(c_expr)), R(cosh(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([sinh(c_expr), cosh(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
t1, t2 = R(sinh(c_expr)), R(cosh(c_expr)) |
|
|
|
p1 = p - c |
|
p_cosh, p_sinh = rs_cosh_sinh(p1, x, prec) |
|
return p_cosh * t2 + p_sinh * t1, p_sinh * t2 + p_cosh * t1 |
|
|
|
t = rs_exp(p, x, prec) |
|
t1 = rs_series_inversion(t, x, prec) |
|
return (t + t1)/2, (t - t1)/2 |
|
|
|
|
|
def _tanh(p, x, prec): |
|
r""" |
|
Helper function of :func:`rs_tanh` |
|
|
|
Return the series expansion of tanh of a univariate series using Newton's |
|
method. It takes advantage of the fact that series expansion of atanh is |
|
easier than that of tanh. |
|
|
|
See Also |
|
======== |
|
|
|
_tanh |
|
""" |
|
R = p.ring |
|
p1 = R(0) |
|
for precx in _giant_steps(prec): |
|
tmp = p - rs_atanh(p1, x, precx) |
|
tmp = rs_mul(tmp, 1 - rs_square(p1, x, prec), x, precx) |
|
p1 += tmp |
|
return p1 |
|
|
|
def rs_tanh(p, x, prec): |
|
""" |
|
Hyperbolic tangent of a series |
|
|
|
Return the series expansion of the tanh of ``p``, about 0. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_tanh |
|
>>> R, x, y = ring('x, y', QQ) |
|
>>> rs_tanh(x + x*y, x, 4) |
|
-1/3*x**3*y**3 - x**3*y**2 - x**3*y - 1/3*x**3 + x*y + x |
|
|
|
See Also |
|
======== |
|
|
|
tanh |
|
""" |
|
if rs_is_puiseux(p, x): |
|
return rs_puiseux(rs_tanh, p, x, prec) |
|
R = p.ring |
|
const = 0 |
|
c = _get_constant_term(p, x) |
|
if c: |
|
try: |
|
c_expr = c.as_expr() |
|
const = R(tanh(c_expr)) |
|
except ValueError: |
|
R = R.add_gens([tanh(c_expr)]) |
|
p = p.set_ring(R) |
|
x = x.set_ring(R) |
|
c = c.set_ring(R) |
|
const = R(tanh(c_expr)) |
|
|
|
p1 = p - c |
|
t1 = rs_tanh(p1, x, prec) |
|
t = rs_series_inversion(1 + const*t1, x, prec) |
|
return rs_mul(const + t1, t, x, prec) |
|
|
|
if R.ngens == 1: |
|
return _tanh(p, x, prec) |
|
else: |
|
return rs_fun(p, _tanh, x, prec) |
|
|
|
def rs_newton(p, x, prec): |
|
""" |
|
Compute the truncated Newton sum of the polynomial ``p`` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_newton |
|
>>> R, x = ring('x', QQ) |
|
>>> p = x**2 - 2 |
|
>>> rs_newton(p, x, 5) |
|
8*x**4 + 4*x**2 + 2 |
|
""" |
|
deg = p.degree() |
|
p1 = _invert_monoms(p) |
|
p2 = rs_series_inversion(p1, x, prec) |
|
p3 = rs_mul(p1.diff(x), p2, x, prec) |
|
res = deg - p3*x |
|
return res |
|
|
|
def rs_hadamard_exp(p1, inverse=False): |
|
""" |
|
Return ``sum f_i/i!*x**i`` from ``sum f_i*x**i``, |
|
where ``x`` is the first variable. |
|
|
|
If ``inverse=True`` return ``sum f_i*i!*x**i`` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_hadamard_exp |
|
>>> R, x = ring('x', QQ) |
|
>>> p = 1 + x + x**2 + x**3 |
|
>>> rs_hadamard_exp(p) |
|
1/6*x**3 + 1/2*x**2 + x + 1 |
|
""" |
|
R = p1.ring |
|
if R.domain != QQ: |
|
raise NotImplementedError |
|
p = R.zero |
|
if not inverse: |
|
for exp1, v1 in p1.items(): |
|
p[exp1] = v1/int(ifac(exp1[0])) |
|
else: |
|
for exp1, v1 in p1.items(): |
|
p[exp1] = v1*int(ifac(exp1[0])) |
|
return p |
|
|
|
def rs_compose_add(p1, p2): |
|
""" |
|
compute the composed sum ``prod(p2(x - beta) for beta root of p1)`` |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.domains import QQ |
|
>>> from sympy.polys.rings import ring |
|
>>> from sympy.polys.ring_series import rs_compose_add |
|
>>> R, x = ring('x', QQ) |
|
>>> f = x**2 - 2 |
|
>>> g = x**2 - 3 |
|
>>> rs_compose_add(f, g) |
|
x**4 - 10*x**2 + 1 |
|
|
|
References |
|
========== |
|
|
|
.. [1] A. Bostan, P. Flajolet, B. Salvy and E. Schost |
|
"Fast Computation with Two Algebraic Numbers", |
|
(2002) Research Report 4579, Institut |
|
National de Recherche en Informatique et en Automatique |
|
""" |
|
R = p1.ring |
|
x = R.gens[0] |
|
prec = p1.degree()*p2.degree() + 1 |
|
np1 = rs_newton(p1, x, prec) |
|
np1e = rs_hadamard_exp(np1) |
|
np2 = rs_newton(p2, x, prec) |
|
np2e = rs_hadamard_exp(np2) |
|
np3e = rs_mul(np1e, np2e, x, prec) |
|
np3 = rs_hadamard_exp(np3e, True) |
|
np3a = (np3[(0,)] - np3) / x |
|
q = rs_integrate(np3a, x) |
|
q = rs_exp(q, x, prec) |
|
q = _invert_monoms(q) |
|
q = q.primitive()[1] |
|
dp = p1.degree()*p2.degree() - q.degree() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if dp: |
|
q = q*x**dp |
|
return q |
|
|
|
|
|
_convert_func = { |
|
'sin': 'rs_sin', |
|
'cos': 'rs_cos', |
|
'exp': 'rs_exp', |
|
'tan': 'rs_tan', |
|
'log': 'rs_log', |
|
'atan': 'rs_atan', |
|
'sinh': 'rs_sinh', |
|
'cosh': 'rs_cosh', |
|
'tanh': 'rs_tanh' |
|
} |
|
|
|
def rs_min_pow(expr, series_rs, a): |
|
"""Find the minimum power of `a` in the series expansion of expr""" |
|
series = 0 |
|
n = 2 |
|
while series == 0: |
|
series = _rs_series(expr, series_rs, a, n) |
|
n *= 2 |
|
R = series.ring |
|
a = R(a) |
|
i = R.gens.index(a) |
|
return min(series, key=lambda t: t[i])[i] |
|
|
|
|
|
def _rs_series(expr, series_rs, a, prec): |
|
|
|
|
|
args = expr.args |
|
R = series_rs.ring |
|
|
|
|
|
if not any(arg.has(Function) for arg in args) and not expr.is_Function: |
|
return series_rs |
|
|
|
if not expr.has(a): |
|
return series_rs |
|
|
|
elif expr.is_Function: |
|
arg = args[0] |
|
if len(args) > 1: |
|
raise NotImplementedError |
|
R1, series = sring(arg, domain=QQ, expand=False, series=True) |
|
series_inner = _rs_series(arg, series, a, prec) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
R = R.compose(R1).compose(series_inner.ring) |
|
series_inner = series_inner.set_ring(R) |
|
series = eval(_convert_func[str(expr.func)])(series_inner, |
|
R(a), prec) |
|
return series |
|
|
|
elif expr.is_Mul: |
|
n = len(args) |
|
for arg in args: |
|
if not arg.is_Number: |
|
R1, _ = sring(arg, expand=False, series=True) |
|
R = R.compose(R1) |
|
min_pows = list(map(rs_min_pow, args, [R(arg) for arg in args], |
|
[a]*len(args))) |
|
sum_pows = sum(min_pows) |
|
series = R(1) |
|
|
|
for i in range(n): |
|
_series = _rs_series(args[i], R(args[i]), a, ceiling(prec |
|
- sum_pows + min_pows[i])) |
|
R = R.compose(_series.ring) |
|
_series = _series.set_ring(R) |
|
series = series.set_ring(R) |
|
series *= _series |
|
series = rs_trunc(series, R(a), prec) |
|
return series |
|
|
|
elif expr.is_Add: |
|
n = len(args) |
|
series = R(0) |
|
for i in range(n): |
|
_series = _rs_series(args[i], R(args[i]), a, prec) |
|
R = R.compose(_series.ring) |
|
_series = _series.set_ring(R) |
|
series = series.set_ring(R) |
|
series += _series |
|
return series |
|
|
|
elif expr.is_Pow: |
|
R1, _ = sring(expr.base, domain=QQ, expand=False, series=True) |
|
R = R.compose(R1) |
|
series_inner = _rs_series(expr.base, R(expr.base), a, prec) |
|
return rs_pow(series_inner, expr.exp, series_inner.ring(a), prec) |
|
|
|
|
|
|
|
elif isinstance(expr, Expr) and expr.is_constant(): |
|
return sring(expr, domain=QQ, expand=False, series=True)[1] |
|
|
|
else: |
|
raise NotImplementedError |
|
|
|
def rs_series(expr, a, prec): |
|
"""Return the series expansion of an expression about 0. |
|
|
|
Parameters |
|
========== |
|
|
|
expr : :class:`~.Expr` |
|
a : :class:`~.Symbol` with respect to which expr is to be expanded |
|
prec : order of the series expansion |
|
|
|
Currently supports multivariate Taylor series expansion. This is much |
|
faster that SymPy's series method as it uses sparse polynomial operations. |
|
|
|
It automatically creates the simplest ring required to represent the series |
|
expansion through repeated calls to sring. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.polys.ring_series import rs_series |
|
>>> from sympy import sin, cos, exp, tan, symbols, QQ |
|
>>> a, b, c = symbols('a, b, c') |
|
>>> rs_series(sin(a) + exp(a), a, 5) |
|
1/24*a**4 + 1/2*a**2 + 2*a + 1 |
|
>>> series = rs_series(tan(a + b)*cos(a + c), a, 2) |
|
>>> series.as_expr() |
|
-a*sin(c)*tan(b) + a*cos(c)*tan(b)**2 + a*cos(c) + cos(c)*tan(b) |
|
>>> series = rs_series(exp(a**QQ(1,3) + a**QQ(2, 5)), a, 1) |
|
>>> series.as_expr() |
|
a**(11/15) + a**(4/5)/2 + a**(2/5) + a**(2/3)/2 + a**(1/3) + 1 |
|
|
|
""" |
|
R, series = sring(expr, domain=QQ, expand=False, series=True) |
|
if a not in R.symbols: |
|
R = R.add_gens([a, ]) |
|
series = series.set_ring(R) |
|
series = _rs_series(expr, series, a, prec) |
|
R = series.ring |
|
gen = R(a) |
|
prec_got = series.degree(gen) + 1 |
|
|
|
if prec_got >= prec: |
|
return rs_trunc(series, gen, prec) |
|
else: |
|
|
|
|
|
|
|
|
|
for more in range(1, 9): |
|
p1 = _rs_series(expr, series, a, prec=prec + more) |
|
gen = gen.set_ring(p1.ring) |
|
new_prec = p1.degree(gen) + 1 |
|
if new_prec != prec_got: |
|
prec_do = ceiling(prec + (prec - prec_got)*more/(new_prec - |
|
prec_got)) |
|
p1 = _rs_series(expr, series, a, prec=prec_do) |
|
while p1.degree(gen) + 1 < prec: |
|
p1 = _rs_series(expr, series, a, prec=prec_do) |
|
gen = gen.set_ring(p1.ring) |
|
prec_do *= 2 |
|
break |
|
else: |
|
break |
|
else: |
|
raise ValueError('Could not calculate %s terms for %s' |
|
% (str(prec), expr)) |
|
return rs_trunc(p1, gen, prec) |
|
|