|
"""Formal Power Series""" |
|
|
|
from collections import defaultdict |
|
|
|
from sympy.core.numbers import (nan, oo, zoo) |
|
from sympy.core.add import Add |
|
from sympy.core.expr import Expr |
|
from sympy.core.function import Derivative, Function, expand |
|
from sympy.core.mul import Mul |
|
from sympy.core.numbers import Rational |
|
from sympy.core.relational import Eq |
|
from sympy.sets.sets import Interval |
|
from sympy.core.singleton import S |
|
from sympy.core.symbol import Wild, Dummy, symbols, Symbol |
|
from sympy.core.sympify import sympify |
|
from sympy.discrete.convolutions import convolution |
|
from sympy.functions.combinatorial.factorials import binomial, factorial, rf |
|
from sympy.functions.combinatorial.numbers import bell |
|
from sympy.functions.elementary.integers import floor, frac, ceiling |
|
from sympy.functions.elementary.miscellaneous import Min, Max |
|
from sympy.functions.elementary.piecewise import Piecewise |
|
from sympy.series.limits import Limit |
|
from sympy.series.order import Order |
|
from sympy.series.sequences import sequence |
|
from sympy.series.series_class import SeriesBase |
|
from sympy.utilities.iterables import iterable |
|
|
|
|
|
|
|
def rational_algorithm(f, x, k, order=4, full=False): |
|
""" |
|
Rational algorithm for computing |
|
formula of coefficients of Formal Power Series |
|
of a function. |
|
|
|
Explanation |
|
=========== |
|
|
|
Applicable when f(x) or some derivative of f(x) |
|
is a rational function in x. |
|
|
|
:func:`rational_algorithm` uses :func:`~.apart` function for partial fraction |
|
decomposition. :func:`~.apart` by default uses 'undetermined coefficients |
|
method'. By setting ``full=True``, 'Bronstein's algorithm' can be used |
|
instead. |
|
|
|
Looks for derivative of a function up to 4'th order (by default). |
|
This can be overridden using order option. |
|
|
|
Parameters |
|
========== |
|
|
|
x : Symbol |
|
order : int, optional |
|
Order of the derivative of ``f``, Default is 4. |
|
full : bool |
|
|
|
Returns |
|
======= |
|
|
|
formula : Expr |
|
ind : Expr |
|
Independent terms. |
|
order : int |
|
full : bool |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import log, atan |
|
>>> from sympy.series.formal import rational_algorithm as ra |
|
>>> from sympy.abc import x, k |
|
|
|
>>> ra(1 / (1 - x), x, k) |
|
(1, 0, 0) |
|
>>> ra(log(1 + x), x, k) |
|
(-1/((-1)**k*k), 0, 1) |
|
|
|
>>> ra(atan(x), x, k, full=True) |
|
((-I/(2*(-I)**k) + I/(2*I**k))/k, 0, 1) |
|
|
|
Notes |
|
===== |
|
|
|
By setting ``full=True``, range of admissible functions to be solved using |
|
``rational_algorithm`` can be increased. This option should be used |
|
carefully as it can significantly slow down the computation as ``doit`` is |
|
performed on the :class:`~.RootSum` object returned by the :func:`~.apart` |
|
function. Use ``full=False`` whenever possible. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.polys.partfrac.apart |
|
|
|
References |
|
========== |
|
|
|
.. [1] Formal Power Series - Dominik Gruntz, Wolfram Koepf |
|
.. [2] Power Series in Computer Algebra - Wolfram Koepf |
|
|
|
""" |
|
from sympy.polys import RootSum, apart |
|
from sympy.integrals import integrate |
|
|
|
diff = f |
|
ds = [] |
|
|
|
for i in range(order + 1): |
|
if i: |
|
diff = diff.diff(x) |
|
|
|
if diff.is_rational_function(x): |
|
coeff, sep = S.Zero, S.Zero |
|
|
|
terms = apart(diff, x, full=full) |
|
if terms.has(RootSum): |
|
terms = terms.doit() |
|
|
|
for t in Add.make_args(terms): |
|
num, den = t.as_numer_denom() |
|
if not den.has(x): |
|
sep += t |
|
else: |
|
if isinstance(den, Mul): |
|
|
|
ind = den.as_independent(x) |
|
den = ind[1] |
|
num /= ind[0] |
|
|
|
|
|
den, j = den.as_base_exp() |
|
a, xterm = den.as_coeff_add(x) |
|
|
|
|
|
if not a: |
|
sep += t |
|
continue |
|
|
|
xc = xterm[0].coeff(x) |
|
a /= -xc |
|
num /= xc**j |
|
|
|
ak = ((-1)**j * num * |
|
binomial(j + k - 1, k).rewrite(factorial) / |
|
a**(j + k)) |
|
coeff += ak |
|
|
|
|
|
if coeff.is_zero: |
|
return None |
|
if (coeff.has(x) or coeff.has(zoo) or coeff.has(oo) or |
|
coeff.has(nan)): |
|
return None |
|
|
|
for j in range(i): |
|
coeff = (coeff / (k + j + 1)) |
|
sep = integrate(sep, x) |
|
sep += (ds.pop() - sep).limit(x, 0) |
|
return (coeff.subs(k, k - i), sep, i) |
|
|
|
else: |
|
ds.append(diff) |
|
|
|
return None |
|
|
|
|
|
def rational_independent(terms, x): |
|
""" |
|
Returns a list of all the rationally independent terms. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import sin, cos |
|
>>> from sympy.series.formal import rational_independent |
|
>>> from sympy.abc import x |
|
|
|
>>> rational_independent([cos(x), sin(x)], x) |
|
[cos(x), sin(x)] |
|
>>> rational_independent([x**2, sin(x), x*sin(x), x**3], x) |
|
[x**3 + x**2, x*sin(x) + sin(x)] |
|
""" |
|
if not terms: |
|
return [] |
|
|
|
ind = terms[0:1] |
|
|
|
for t in terms[1:]: |
|
n = t.as_independent(x)[1] |
|
for i, term in enumerate(ind): |
|
d = term.as_independent(x)[1] |
|
q = (n / d).cancel() |
|
if q.is_rational_function(x): |
|
ind[i] += t |
|
break |
|
else: |
|
ind.append(t) |
|
return ind |
|
|
|
|
|
def simpleDE(f, x, g, order=4): |
|
r""" |
|
Generates simple DE. |
|
|
|
Explanation |
|
=========== |
|
|
|
DE is of the form |
|
|
|
.. math:: |
|
f^k(x) + \sum\limits_{j=0}^{k-1} A_j f^j(x) = 0 |
|
|
|
where :math:`A_j` should be rational function in x. |
|
|
|
Generates DE's upto order 4 (default). DE's can also have free parameters. |
|
|
|
By increasing order, higher order DE's can be found. |
|
|
|
Yields a tuple of (DE, order). |
|
""" |
|
from sympy.solvers.solveset import linsolve |
|
|
|
a = symbols('a:%d' % (order)) |
|
|
|
def _makeDE(k): |
|
eq = f.diff(x, k) + Add(*[a[i]*f.diff(x, i) for i in range(0, k)]) |
|
DE = g(x).diff(x, k) + Add(*[a[i]*g(x).diff(x, i) for i in range(0, k)]) |
|
return eq, DE |
|
|
|
found = False |
|
for k in range(1, order + 1): |
|
eq, DE = _makeDE(k) |
|
eq = eq.expand() |
|
terms = eq.as_ordered_terms() |
|
ind = rational_independent(terms, x) |
|
if found or len(ind) == k: |
|
sol = dict(zip(a, (i for s in linsolve(ind, a[:k]) for i in s))) |
|
if sol: |
|
found = True |
|
DE = DE.subs(sol) |
|
DE = DE.as_numer_denom()[0] |
|
DE = DE.factor().as_coeff_mul(Derivative)[1][0] |
|
yield DE.collect(Derivative(g(x))), k |
|
|
|
|
|
def exp_re(DE, r, k): |
|
"""Converts a DE with constant coefficients (explike) into a RE. |
|
|
|
Explanation |
|
=========== |
|
|
|
Performs the substitution: |
|
|
|
.. math:: |
|
f^j(x) \\to r(k + j) |
|
|
|
Normalises the terms so that lowest order of a term is always r(k). |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Function, Derivative |
|
>>> from sympy.series.formal import exp_re |
|
>>> from sympy.abc import x, k |
|
>>> f, r = Function('f'), Function('r') |
|
|
|
>>> exp_re(-f(x) + Derivative(f(x)), r, k) |
|
-r(k) + r(k + 1) |
|
>>> exp_re(Derivative(f(x), x) + Derivative(f(x), (x, 2)), r, k) |
|
r(k) + r(k + 1) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.hyper_re |
|
""" |
|
RE = S.Zero |
|
|
|
g = DE.atoms(Function).pop() |
|
|
|
mini = None |
|
for t in Add.make_args(DE): |
|
coeff, d = t.as_independent(g) |
|
if isinstance(d, Derivative): |
|
j = d.derivative_count |
|
else: |
|
j = 0 |
|
if mini is None or j < mini: |
|
mini = j |
|
RE += coeff * r(k + j) |
|
if mini: |
|
RE = RE.subs(k, k - mini) |
|
return RE |
|
|
|
|
|
def hyper_re(DE, r, k): |
|
""" |
|
Converts a DE into a RE. |
|
|
|
Explanation |
|
=========== |
|
|
|
Performs the substitution: |
|
|
|
.. math:: |
|
x^l f^j(x) \\to (k + 1 - l)_j . a_{k + j - l} |
|
|
|
Normalises the terms so that lowest order of a term is always r(k). |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Function, Derivative |
|
>>> from sympy.series.formal import hyper_re |
|
>>> from sympy.abc import x, k |
|
>>> f, r = Function('f'), Function('r') |
|
|
|
>>> hyper_re(-f(x) + Derivative(f(x)), r, k) |
|
(k + 1)*r(k + 1) - r(k) |
|
>>> hyper_re(-x*f(x) + Derivative(f(x), (x, 2)), r, k) |
|
(k + 2)*(k + 3)*r(k + 3) - r(k) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.exp_re |
|
""" |
|
RE = S.Zero |
|
|
|
g = DE.atoms(Function).pop() |
|
x = g.atoms(Symbol).pop() |
|
|
|
mini = None |
|
for t in Add.make_args(DE.expand()): |
|
coeff, d = t.as_independent(g) |
|
c, v = coeff.as_independent(x) |
|
l = v.as_coeff_exponent(x)[1] |
|
if isinstance(d, Derivative): |
|
j = d.derivative_count |
|
else: |
|
j = 0 |
|
RE += c * rf(k + 1 - l, j) * r(k + j - l) |
|
if mini is None or j - l < mini: |
|
mini = j - l |
|
|
|
RE = RE.subs(k, k - mini) |
|
|
|
m = Wild('m') |
|
return RE.collect(r(k + m)) |
|
|
|
|
|
def _transformation_a(f, x, P, Q, k, m, shift): |
|
f *= x**(-shift) |
|
P = P.subs(k, k + shift) |
|
Q = Q.subs(k, k + shift) |
|
return f, P, Q, m |
|
|
|
|
|
def _transformation_c(f, x, P, Q, k, m, scale): |
|
f = f.subs(x, x**scale) |
|
P = P.subs(k, k / scale) |
|
Q = Q.subs(k, k / scale) |
|
m *= scale |
|
return f, P, Q, m |
|
|
|
|
|
def _transformation_e(f, x, P, Q, k, m): |
|
f = f.diff(x) |
|
P = P.subs(k, k + 1) * (k + m + 1) |
|
Q = Q.subs(k, k + 1) * (k + 1) |
|
return f, P, Q, m |
|
|
|
|
|
def _apply_shift(sol, shift): |
|
return [(res, cond + shift) for res, cond in sol] |
|
|
|
|
|
def _apply_scale(sol, scale): |
|
return [(res, cond / scale) for res, cond in sol] |
|
|
|
|
|
def _apply_integrate(sol, x, k): |
|
return [(res / ((cond + 1)*(cond.as_coeff_Add()[1].coeff(k))), cond + 1) |
|
for res, cond in sol] |
|
|
|
|
|
def _compute_formula(f, x, P, Q, k, m, k_max): |
|
"""Computes the formula for f.""" |
|
from sympy.polys import roots |
|
|
|
sol = [] |
|
for i in range(k_max + 1, k_max + m + 1): |
|
if (i < 0) == True: |
|
continue |
|
r = f.diff(x, i).limit(x, 0) / factorial(i) |
|
if r.is_zero: |
|
continue |
|
|
|
kterm = m*k + i |
|
res = r |
|
|
|
p = P.subs(k, kterm) |
|
q = Q.subs(k, kterm) |
|
c1 = p.subs(k, 1/k).leadterm(k)[0] |
|
c2 = q.subs(k, 1/k).leadterm(k)[0] |
|
res *= (-c1 / c2)**k |
|
|
|
res *= Mul(*[rf(-r, k)**mul for r, mul in roots(p, k).items()]) |
|
res /= Mul(*[rf(-r, k)**mul for r, mul in roots(q, k).items()]) |
|
|
|
sol.append((res, kterm)) |
|
|
|
return sol |
|
|
|
|
|
def _rsolve_hypergeometric(f, x, P, Q, k, m): |
|
""" |
|
Recursive wrapper to rsolve_hypergeometric. |
|
|
|
Explanation |
|
=========== |
|
|
|
Returns a Tuple of (formula, series independent terms, |
|
maximum power of x in independent terms) if successful |
|
otherwise ``None``. |
|
|
|
See :func:`rsolve_hypergeometric` for details. |
|
""" |
|
from sympy.polys import lcm, roots |
|
from sympy.integrals import integrate |
|
|
|
|
|
proots, qroots = roots(P, k), roots(Q, k) |
|
all_roots = dict(proots) |
|
all_roots.update(qroots) |
|
scale = lcm([r.as_numer_denom()[1] for r, t in all_roots.items() |
|
if r.is_rational]) |
|
f, P, Q, m = _transformation_c(f, x, P, Q, k, m, scale) |
|
|
|
|
|
qroots = roots(Q, k) |
|
if qroots: |
|
k_min = Min(*qroots.keys()) |
|
else: |
|
k_min = S.Zero |
|
shift = k_min + m |
|
f, P, Q, m = _transformation_a(f, x, P, Q, k, m, shift) |
|
|
|
l = (x*f).limit(x, 0) |
|
if not isinstance(l, Limit) and l != 0: |
|
return None |
|
|
|
qroots = roots(Q, k) |
|
if qroots: |
|
k_max = Max(*qroots.keys()) |
|
else: |
|
k_max = S.Zero |
|
|
|
ind, mp = S.Zero, -oo |
|
for i in range(k_max + m + 1): |
|
r = f.diff(x, i).limit(x, 0) / factorial(i) |
|
if r.is_finite is False: |
|
old_f = f |
|
f, P, Q, m = _transformation_a(f, x, P, Q, k, m, i) |
|
f, P, Q, m = _transformation_e(f, x, P, Q, k, m) |
|
sol, ind, mp = _rsolve_hypergeometric(f, x, P, Q, k, m) |
|
sol = _apply_integrate(sol, x, k) |
|
sol = _apply_shift(sol, i) |
|
ind = integrate(ind, x) |
|
ind += (old_f - ind).limit(x, 0) |
|
mp += 1 |
|
return sol, ind, mp |
|
elif r: |
|
ind += r*x**(i + shift) |
|
pow_x = Rational((i + shift), scale) |
|
if pow_x > mp: |
|
mp = pow_x |
|
ind = ind.subs(x, x**(1/scale)) |
|
|
|
sol = _compute_formula(f, x, P, Q, k, m, k_max) |
|
sol = _apply_shift(sol, shift) |
|
sol = _apply_scale(sol, scale) |
|
|
|
return sol, ind, mp |
|
|
|
|
|
def rsolve_hypergeometric(f, x, P, Q, k, m): |
|
""" |
|
Solves RE of hypergeometric type. |
|
|
|
Explanation |
|
=========== |
|
|
|
Attempts to solve RE of the form |
|
|
|
Q(k)*a(k + m) - P(k)*a(k) |
|
|
|
Transformations that preserve Hypergeometric type: |
|
|
|
a. x**n*f(x): b(k + m) = R(k - n)*b(k) |
|
b. f(A*x): b(k + m) = A**m*R(k)*b(k) |
|
c. f(x**n): b(k + n*m) = R(k/n)*b(k) |
|
d. f(x**(1/m)): b(k + 1) = R(k*m)*b(k) |
|
e. f'(x): b(k + m) = ((k + m + 1)/(k + 1))*R(k + 1)*b(k) |
|
|
|
Some of these transformations have been used to solve the RE. |
|
|
|
Returns |
|
======= |
|
|
|
formula : Expr |
|
ind : Expr |
|
Independent terms. |
|
order : int |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import exp, ln, S |
|
>>> from sympy.series.formal import rsolve_hypergeometric as rh |
|
>>> from sympy.abc import x, k |
|
|
|
>>> rh(exp(x), x, -S.One, (k + 1), k, 1) |
|
(Piecewise((1/factorial(k), Eq(Mod(k, 1), 0)), (0, True)), 1, 1) |
|
|
|
>>> rh(ln(1 + x), x, k**2, k*(k + 1), k, 1) |
|
(Piecewise(((-1)**(k - 1)*factorial(k - 1)/RisingFactorial(2, k - 1), |
|
Eq(Mod(k, 1), 0)), (0, True)), x, 2) |
|
|
|
References |
|
========== |
|
|
|
.. [1] Formal Power Series - Dominik Gruntz, Wolfram Koepf |
|
.. [2] Power Series in Computer Algebra - Wolfram Koepf |
|
""" |
|
result = _rsolve_hypergeometric(f, x, P, Q, k, m) |
|
|
|
if result is None: |
|
return None |
|
|
|
sol_list, ind, mp = result |
|
|
|
sol_dict = defaultdict(lambda: S.Zero) |
|
for res, cond in sol_list: |
|
j, mk = cond.as_coeff_Add() |
|
c = mk.coeff(k) |
|
|
|
if j.is_integer is False: |
|
res *= x**frac(j) |
|
j = floor(j) |
|
|
|
res = res.subs(k, (k - j) / c) |
|
cond = Eq(k % c, j % c) |
|
sol_dict[cond] += res |
|
|
|
sol = [(res, cond) for cond, res in sol_dict.items()] |
|
sol.append((S.Zero, True)) |
|
sol = Piecewise(*sol) |
|
|
|
if mp is -oo: |
|
s = S.Zero |
|
elif mp.is_integer is False: |
|
s = ceiling(mp) |
|
else: |
|
s = mp + 1 |
|
|
|
|
|
|
|
if s < 0: |
|
ind += sum(sequence(sol * x**k, (k, s, -1))) |
|
s = S.Zero |
|
|
|
return (sol, ind, s) |
|
|
|
|
|
def _solve_hyper_RE(f, x, RE, g, k): |
|
"""See docstring of :func:`rsolve_hypergeometric` for details.""" |
|
terms = Add.make_args(RE) |
|
|
|
if len(terms) == 2: |
|
gs = list(RE.atoms(Function)) |
|
P, Q = map(RE.coeff, gs) |
|
m = gs[1].args[0] - gs[0].args[0] |
|
if m < 0: |
|
P, Q = Q, P |
|
m = abs(m) |
|
return rsolve_hypergeometric(f, x, P, Q, k, m) |
|
|
|
|
|
def _solve_explike_DE(f, x, DE, g, k): |
|
"""Solves DE with constant coefficients.""" |
|
from sympy.solvers import rsolve |
|
|
|
for t in Add.make_args(DE): |
|
coeff, d = t.as_independent(g) |
|
if coeff.free_symbols: |
|
return |
|
|
|
RE = exp_re(DE, g, k) |
|
|
|
init = {} |
|
for i in range(len(Add.make_args(RE))): |
|
if i: |
|
f = f.diff(x) |
|
init[g(k).subs(k, i)] = f.limit(x, 0) |
|
|
|
sol = rsolve(RE, g(k), init) |
|
|
|
if sol: |
|
return (sol / factorial(k), S.Zero, S.Zero) |
|
|
|
|
|
def _solve_simple(f, x, DE, g, k): |
|
"""Converts DE into RE and solves using :func:`rsolve`.""" |
|
from sympy.solvers import rsolve |
|
|
|
RE = hyper_re(DE, g, k) |
|
|
|
init = {} |
|
for i in range(len(Add.make_args(RE))): |
|
if i: |
|
f = f.diff(x) |
|
init[g(k).subs(k, i)] = f.limit(x, 0) / factorial(i) |
|
|
|
sol = rsolve(RE, g(k), init) |
|
|
|
if sol: |
|
return (sol, S.Zero, S.Zero) |
|
|
|
|
|
def _transform_explike_DE(DE, g, x, order, syms): |
|
"""Converts DE with free parameters into DE with constant coefficients.""" |
|
from sympy.solvers.solveset import linsolve |
|
|
|
eq = [] |
|
highest_coeff = DE.coeff(Derivative(g(x), x, order)) |
|
for i in range(order): |
|
coeff = DE.coeff(Derivative(g(x), x, i)) |
|
coeff = (coeff / highest_coeff).expand().collect(x) |
|
eq.extend(Add.make_args(coeff)) |
|
temp = [] |
|
for e in eq: |
|
if e.has(x): |
|
break |
|
elif e.has(Symbol): |
|
temp.append(e) |
|
else: |
|
eq = temp |
|
if eq: |
|
sol = dict(zip(syms, (i for s in linsolve(eq, list(syms)) for i in s))) |
|
if sol: |
|
DE = DE.subs(sol) |
|
DE = DE.factor().as_coeff_mul(Derivative)[1][0] |
|
DE = DE.collect(Derivative(g(x))) |
|
return DE |
|
|
|
|
|
def _transform_DE_RE(DE, g, k, order, syms): |
|
"""Converts DE with free parameters into RE of hypergeometric type.""" |
|
from sympy.solvers.solveset import linsolve |
|
|
|
RE = hyper_re(DE, g, k) |
|
|
|
eq = [RE.coeff(g(k + i)) for i in range(1, order)] |
|
sol = dict(zip(syms, (i for s in linsolve(eq, list(syms)) for i in s))) |
|
if sol: |
|
m = Wild('m') |
|
RE = RE.subs(sol) |
|
RE = RE.factor().as_numer_denom()[0].collect(g(k + m)) |
|
RE = RE.as_coeff_mul(g)[1][0] |
|
for i in range(order): |
|
if RE.coeff(g(k + i)) and i: |
|
RE = RE.subs(k, k - i) |
|
break |
|
return RE |
|
|
|
|
|
def solve_de(f, x, DE, order, g, k): |
|
""" |
|
Solves the DE. |
|
|
|
Explanation |
|
=========== |
|
|
|
Tries to solve DE by either converting into a RE containing two terms or |
|
converting into a DE having constant coefficients. |
|
|
|
Returns |
|
======= |
|
|
|
formula : Expr |
|
ind : Expr |
|
Independent terms. |
|
order : int |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Derivative as D, Function |
|
>>> from sympy import exp, ln |
|
>>> from sympy.series.formal import solve_de |
|
>>> from sympy.abc import x, k |
|
>>> f = Function('f') |
|
|
|
>>> solve_de(exp(x), x, D(f(x), x) - f(x), 1, f, k) |
|
(Piecewise((1/factorial(k), Eq(Mod(k, 1), 0)), (0, True)), 1, 1) |
|
|
|
>>> solve_de(ln(1 + x), x, (x + 1)*D(f(x), x, 2) + D(f(x)), 2, f, k) |
|
(Piecewise(((-1)**(k - 1)*factorial(k - 1)/RisingFactorial(2, k - 1), |
|
Eq(Mod(k, 1), 0)), (0, True)), x, 2) |
|
""" |
|
sol = None |
|
syms = DE.free_symbols.difference({g, x}) |
|
|
|
if syms: |
|
RE = _transform_DE_RE(DE, g, k, order, syms) |
|
else: |
|
RE = hyper_re(DE, g, k) |
|
if not RE.free_symbols.difference({k}): |
|
sol = _solve_hyper_RE(f, x, RE, g, k) |
|
|
|
if sol: |
|
return sol |
|
|
|
if syms: |
|
DE = _transform_explike_DE(DE, g, x, order, syms) |
|
if not DE.free_symbols.difference({x}): |
|
sol = _solve_explike_DE(f, x, DE, g, k) |
|
|
|
if sol: |
|
return sol |
|
|
|
|
|
def hyper_algorithm(f, x, k, order=4): |
|
""" |
|
Hypergeometric algorithm for computing Formal Power Series. |
|
|
|
Explanation |
|
=========== |
|
|
|
Steps: |
|
* Generates DE |
|
* Convert the DE into RE |
|
* Solves the RE |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import exp, ln |
|
>>> from sympy.series.formal import hyper_algorithm |
|
|
|
>>> from sympy.abc import x, k |
|
|
|
>>> hyper_algorithm(exp(x), x, k) |
|
(Piecewise((1/factorial(k), Eq(Mod(k, 1), 0)), (0, True)), 1, 1) |
|
|
|
>>> hyper_algorithm(ln(1 + x), x, k) |
|
(Piecewise(((-1)**(k - 1)*factorial(k - 1)/RisingFactorial(2, k - 1), |
|
Eq(Mod(k, 1), 0)), (0, True)), x, 2) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.simpleDE |
|
sympy.series.formal.solve_de |
|
""" |
|
g = Function('g') |
|
|
|
des = [] |
|
sol = None |
|
for DE, i in simpleDE(f, x, g, order): |
|
if DE is not None: |
|
sol = solve_de(f, x, DE, i, g, k) |
|
if sol: |
|
return sol |
|
if not DE.free_symbols.difference({x}): |
|
des.append(DE) |
|
|
|
|
|
|
|
for DE in des: |
|
sol = _solve_simple(f, x, DE, g, k) |
|
if sol: |
|
return sol |
|
|
|
|
|
def _compute_fps(f, x, x0, dir, hyper, order, rational, full): |
|
"""Recursive wrapper to compute fps. |
|
|
|
See :func:`compute_fps` for details. |
|
""" |
|
if x0 in [S.Infinity, S.NegativeInfinity]: |
|
dir = S.One if x0 is S.Infinity else -S.One |
|
temp = f.subs(x, 1/x) |
|
result = _compute_fps(temp, x, 0, dir, hyper, order, rational, full) |
|
if result is None: |
|
return None |
|
return (result[0], result[1].subs(x, 1/x), result[2].subs(x, 1/x)) |
|
elif x0 or dir == -S.One: |
|
if dir == -S.One: |
|
rep = -x + x0 |
|
rep2 = -x |
|
rep2b = x0 |
|
else: |
|
rep = x + x0 |
|
rep2 = x |
|
rep2b = -x0 |
|
temp = f.subs(x, rep) |
|
result = _compute_fps(temp, x, 0, S.One, hyper, order, rational, full) |
|
if result is None: |
|
return None |
|
return (result[0], result[1].subs(x, rep2 + rep2b), |
|
result[2].subs(x, rep2 + rep2b)) |
|
|
|
if f.is_polynomial(x): |
|
k = Dummy('k') |
|
ak = sequence(Coeff(f, x, k), (k, 1, oo)) |
|
xk = sequence(x**k, (k, 0, oo)) |
|
ind = f.coeff(x, 0) |
|
return ak, xk, ind |
|
|
|
|
|
|
|
|
|
|
|
if isinstance(f, Add): |
|
result = False |
|
ak = sequence(S.Zero, (0, oo)) |
|
ind, xk = S.Zero, None |
|
for t in Add.make_args(f): |
|
res = _compute_fps(t, x, 0, S.One, hyper, order, rational, full) |
|
if res: |
|
if not result: |
|
result = True |
|
xk = res[1] |
|
if res[0].start > ak.start: |
|
seq = ak |
|
s, f = ak.start, res[0].start |
|
else: |
|
seq = res[0] |
|
s, f = res[0].start, ak.start |
|
save = Add(*[z[0]*z[1] for z in zip(seq[0:(f - s)], xk[s:f])]) |
|
ak += res[0] |
|
ind += res[2] + save |
|
else: |
|
ind += t |
|
if result: |
|
return ak, xk, ind |
|
return None |
|
|
|
|
|
|
|
syms = f.free_symbols.difference({x}) |
|
(f, symb) = expand(f).as_independent(*syms) |
|
|
|
result = None |
|
|
|
|
|
k = Dummy('k') |
|
if rational: |
|
result = rational_algorithm(f, x, k, order, full) |
|
|
|
if result is None and hyper: |
|
result = hyper_algorithm(f, x, k, order) |
|
|
|
if result is None: |
|
return None |
|
|
|
from sympy.simplify.powsimp import powsimp |
|
if symb.is_zero: |
|
symb = S.One |
|
else: |
|
symb = powsimp(symb) |
|
ak = sequence(result[0], (k, result[2], oo)) |
|
xk_formula = powsimp(x**k * symb) |
|
xk = sequence(xk_formula, (k, 0, oo)) |
|
ind = powsimp(result[1] * symb) |
|
|
|
return ak, xk, ind |
|
|
|
|
|
def compute_fps(f, x, x0=0, dir=1, hyper=True, order=4, rational=True, |
|
full=False): |
|
""" |
|
Computes the formula for Formal Power Series of a function. |
|
|
|
Explanation |
|
=========== |
|
|
|
Tries to compute the formula by applying the following techniques |
|
(in order): |
|
|
|
* rational_algorithm |
|
* Hypergeometric algorithm |
|
|
|
Parameters |
|
========== |
|
|
|
x : Symbol |
|
x0 : number, optional |
|
Point to perform series expansion about. Default is 0. |
|
dir : {1, -1, '+', '-'}, optional |
|
If dir is 1 or '+' the series is calculated from the right and |
|
for -1 or '-' the series is calculated from the left. For smooth |
|
functions this flag will not alter the results. Default is 1. |
|
hyper : {True, False}, optional |
|
Set hyper to False to skip the hypergeometric algorithm. |
|
By default it is set to False. |
|
order : int, optional |
|
Order of the derivative of ``f``, Default is 4. |
|
rational : {True, False}, optional |
|
Set rational to False to skip rational algorithm. By default it is set |
|
to True. |
|
full : {True, False}, optional |
|
Set full to True to increase the range of rational algorithm. |
|
See :func:`rational_algorithm` for details. By default it is set to |
|
False. |
|
|
|
Returns |
|
======= |
|
|
|
ak : sequence |
|
Sequence of coefficients. |
|
xk : sequence |
|
Sequence of powers of x. |
|
ind : Expr |
|
Independent terms. |
|
mul : Pow |
|
Common terms. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.rational_algorithm |
|
sympy.series.formal.hyper_algorithm |
|
""" |
|
f = sympify(f) |
|
x = sympify(x) |
|
|
|
if not f.has(x): |
|
return None |
|
|
|
x0 = sympify(x0) |
|
|
|
if dir == '+': |
|
dir = S.One |
|
elif dir == '-': |
|
dir = -S.One |
|
elif dir not in [S.One, -S.One]: |
|
raise ValueError("Dir must be '+' or '-'") |
|
else: |
|
dir = sympify(dir) |
|
|
|
return _compute_fps(f, x, x0, dir, hyper, order, rational, full) |
|
|
|
|
|
class Coeff(Function): |
|
""" |
|
Coeff(p, x, n) represents the nth coefficient of the polynomial p in x |
|
""" |
|
@classmethod |
|
def eval(cls, p, x, n): |
|
if p.is_polynomial(x) and n.is_integer: |
|
return p.coeff(x, n) |
|
|
|
|
|
class FormalPowerSeries(SeriesBase): |
|
""" |
|
Represents Formal Power Series of a function. |
|
|
|
Explanation |
|
=========== |
|
|
|
No computation is performed. This class should only to be used to represent |
|
a series. No checks are performed. |
|
|
|
For computing a series use :func:`fps`. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.fps |
|
""" |
|
def __new__(cls, *args): |
|
args = map(sympify, args) |
|
return Expr.__new__(cls, *args) |
|
|
|
def __init__(self, *args): |
|
ak = args[4][0] |
|
k = ak.variables[0] |
|
self.ak_seq = sequence(ak.formula, (k, 1, oo)) |
|
self.fact_seq = sequence(factorial(k), (k, 1, oo)) |
|
self.bell_coeff_seq = self.ak_seq * self.fact_seq |
|
self.sign_seq = sequence((-1, 1), (k, 1, oo)) |
|
|
|
@property |
|
def function(self): |
|
return self.args[0] |
|
|
|
@property |
|
def x(self): |
|
return self.args[1] |
|
|
|
@property |
|
def x0(self): |
|
return self.args[2] |
|
|
|
@property |
|
def dir(self): |
|
return self.args[3] |
|
|
|
@property |
|
def ak(self): |
|
return self.args[4][0] |
|
|
|
@property |
|
def xk(self): |
|
return self.args[4][1] |
|
|
|
@property |
|
def ind(self): |
|
return self.args[4][2] |
|
|
|
@property |
|
def interval(self): |
|
return Interval(0, oo) |
|
|
|
@property |
|
def start(self): |
|
return self.interval.inf |
|
|
|
@property |
|
def stop(self): |
|
return self.interval.sup |
|
|
|
@property |
|
def length(self): |
|
return oo |
|
|
|
@property |
|
def infinite(self): |
|
"""Returns an infinite representation of the series""" |
|
from sympy.concrete import Sum |
|
ak, xk = self.ak, self.xk |
|
k = ak.variables[0] |
|
inf_sum = Sum(ak.formula * xk.formula, (k, ak.start, ak.stop)) |
|
|
|
return self.ind + inf_sum |
|
|
|
def _get_pow_x(self, term): |
|
"""Returns the power of x in a term.""" |
|
xterm, pow_x = term.as_independent(self.x)[1].as_base_exp() |
|
if not xterm.has(self.x): |
|
return S.Zero |
|
return pow_x |
|
|
|
def polynomial(self, n=6): |
|
""" |
|
Truncated series as polynomial. |
|
|
|
Explanation |
|
=========== |
|
|
|
Returns series expansion of ``f`` upto order ``O(x**n)`` |
|
as a polynomial(without ``O`` term). |
|
""" |
|
terms = [] |
|
sym = self.free_symbols |
|
for i, t in enumerate(self): |
|
xp = self._get_pow_x(t) |
|
if xp.has(*sym): |
|
xp = xp.as_coeff_add(*sym)[0] |
|
if xp >= n: |
|
break |
|
elif xp.is_integer is True and i == n + 1: |
|
break |
|
elif t is not S.Zero: |
|
terms.append(t) |
|
|
|
return Add(*terms) |
|
|
|
def truncate(self, n=6): |
|
""" |
|
Truncated series. |
|
|
|
Explanation |
|
=========== |
|
|
|
Returns truncated series expansion of f upto |
|
order ``O(x**n)``. |
|
|
|
If n is ``None``, returns an infinite iterator. |
|
""" |
|
if n is None: |
|
return iter(self) |
|
|
|
x, x0 = self.x, self.x0 |
|
pt_xk = self.xk.coeff(n) |
|
if x0 is S.NegativeInfinity: |
|
x0 = S.Infinity |
|
|
|
return self.polynomial(n) + Order(pt_xk, (x, x0)) |
|
|
|
def zero_coeff(self): |
|
return self._eval_term(0) |
|
|
|
def _eval_term(self, pt): |
|
try: |
|
pt_xk = self.xk.coeff(pt) |
|
pt_ak = self.ak.coeff(pt).simplify() |
|
except IndexError: |
|
term = S.Zero |
|
else: |
|
term = (pt_ak * pt_xk) |
|
|
|
if self.ind: |
|
ind = S.Zero |
|
sym = self.free_symbols |
|
for t in Add.make_args(self.ind): |
|
pow_x = self._get_pow_x(t) |
|
if pow_x.has(*sym): |
|
pow_x = pow_x.as_coeff_add(*sym)[0] |
|
if pt == 0 and pow_x < 1: |
|
ind += t |
|
elif pow_x >= pt and pow_x < pt + 1: |
|
ind += t |
|
term += ind |
|
|
|
return term.collect(self.x) |
|
|
|
def _eval_subs(self, old, new): |
|
x = self.x |
|
if old.has(x): |
|
return self |
|
|
|
def _eval_as_leading_term(self, x, logx, cdir): |
|
for t in self: |
|
if t is not S.Zero: |
|
return t |
|
|
|
def _eval_derivative(self, x): |
|
f = self.function.diff(x) |
|
ind = self.ind.diff(x) |
|
|
|
pow_xk = self._get_pow_x(self.xk.formula) |
|
ak = self.ak |
|
k = ak.variables[0] |
|
if ak.formula.has(x): |
|
form = [] |
|
for e, c in ak.formula.args: |
|
temp = S.Zero |
|
for t in Add.make_args(e): |
|
pow_x = self._get_pow_x(t) |
|
temp += t * (pow_xk + pow_x) |
|
form.append((temp, c)) |
|
form = Piecewise(*form) |
|
ak = sequence(form.subs(k, k + 1), (k, ak.start - 1, ak.stop)) |
|
else: |
|
ak = sequence((ak.formula * pow_xk).subs(k, k + 1), |
|
(k, ak.start - 1, ak.stop)) |
|
|
|
return self.func(f, self.x, self.x0, self.dir, (ak, self.xk, ind)) |
|
|
|
def integrate(self, x=None, **kwargs): |
|
""" |
|
Integrate Formal Power Series. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import fps, sin, integrate |
|
>>> from sympy.abc import x |
|
>>> f = fps(sin(x)) |
|
>>> f.integrate(x).truncate() |
|
-1 + x**2/2 - x**4/24 + O(x**6) |
|
>>> integrate(f, (x, 0, 1)) |
|
1 - cos(1) |
|
""" |
|
from sympy.integrals import integrate |
|
|
|
if x is None: |
|
x = self.x |
|
elif iterable(x): |
|
return integrate(self.function, x) |
|
|
|
f = integrate(self.function, x) |
|
ind = integrate(self.ind, x) |
|
ind += (f - ind).limit(x, 0) |
|
|
|
pow_xk = self._get_pow_x(self.xk.formula) |
|
ak = self.ak |
|
k = ak.variables[0] |
|
if ak.formula.has(x): |
|
form = [] |
|
for e, c in ak.formula.args: |
|
temp = S.Zero |
|
for t in Add.make_args(e): |
|
pow_x = self._get_pow_x(t) |
|
temp += t / (pow_xk + pow_x + 1) |
|
form.append((temp, c)) |
|
form = Piecewise(*form) |
|
ak = sequence(form.subs(k, k - 1), (k, ak.start + 1, ak.stop)) |
|
else: |
|
ak = sequence((ak.formula / (pow_xk + 1)).subs(k, k - 1), |
|
(k, ak.start + 1, ak.stop)) |
|
|
|
return self.func(f, self.x, self.x0, self.dir, (ak, self.xk, ind)) |
|
|
|
def product(self, other, x=None, n=6): |
|
""" |
|
Multiplies two Formal Power Series, using discrete convolution and |
|
return the truncated terms upto specified order. |
|
|
|
Parameters |
|
========== |
|
|
|
n : Number, optional |
|
Specifies the order of the term up to which the polynomial should |
|
be truncated. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import fps, sin, exp |
|
>>> from sympy.abc import x |
|
>>> f1 = fps(sin(x)) |
|
>>> f2 = fps(exp(x)) |
|
|
|
>>> f1.product(f2, x).truncate(4) |
|
x + x**2 + x**3/3 + O(x**4) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.discrete.convolutions |
|
sympy.series.formal.FormalPowerSeriesProduct |
|
|
|
""" |
|
|
|
if n is None: |
|
return iter(self) |
|
|
|
other = sympify(other) |
|
|
|
if not isinstance(other, FormalPowerSeries): |
|
raise ValueError("Both series should be an instance of FormalPowerSeries" |
|
" class.") |
|
|
|
if self.dir != other.dir: |
|
raise ValueError("Both series should be calculated from the" |
|
" same direction.") |
|
elif self.x0 != other.x0: |
|
raise ValueError("Both series should be calculated about the" |
|
" same point.") |
|
|
|
elif self.x != other.x: |
|
raise ValueError("Both series should have the same symbol.") |
|
|
|
return FormalPowerSeriesProduct(self, other) |
|
|
|
def coeff_bell(self, n): |
|
r""" |
|
self.coeff_bell(n) returns a sequence of Bell polynomials of the second kind. |
|
Note that ``n`` should be a integer. |
|
|
|
The second kind of Bell polynomials (are sometimes called "partial" Bell |
|
polynomials or incomplete Bell polynomials) are defined as |
|
|
|
.. math:: |
|
B_{n,k}(x_1, x_2,\dotsc x_{n-k+1}) = |
|
\sum_{j_1+j_2+j_2+\dotsb=k \atop j_1+2j_2+3j_2+\dotsb=n} |
|
\frac{n!}{j_1!j_2!\dotsb j_{n-k+1}!} |
|
\left(\frac{x_1}{1!} \right)^{j_1} |
|
\left(\frac{x_2}{2!} \right)^{j_2} \dotsb |
|
\left(\frac{x_{n-k+1}}{(n-k+1)!} \right) ^{j_{n-k+1}}. |
|
|
|
* ``bell(n, k, (x1, x2, ...))`` gives Bell polynomials of the second kind, |
|
`B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1})`. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.functions.combinatorial.numbers.bell |
|
|
|
""" |
|
|
|
inner_coeffs = [bell(n, j, tuple(self.bell_coeff_seq[:n-j+1])) for j in range(1, n+1)] |
|
|
|
k = Dummy('k') |
|
return sequence(tuple(inner_coeffs), (k, 1, oo)) |
|
|
|
def compose(self, other, x=None, n=6): |
|
r""" |
|
Returns the truncated terms of the formal power series of the composed function, |
|
up to specified ``n``. |
|
|
|
Explanation |
|
=========== |
|
|
|
If ``f`` and ``g`` are two formal power series of two different functions, |
|
then the coefficient sequence ``ak`` of the composed formal power series `fp` |
|
will be as follows. |
|
|
|
.. math:: |
|
\sum\limits_{k=0}^{n} b_k B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1}) |
|
|
|
Parameters |
|
========== |
|
|
|
n : Number, optional |
|
Specifies the order of the term up to which the polynomial should |
|
be truncated. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import fps, sin, exp |
|
>>> from sympy.abc import x |
|
>>> f1 = fps(exp(x)) |
|
>>> f2 = fps(sin(x)) |
|
|
|
>>> f1.compose(f2, x).truncate() |
|
1 + x + x**2/2 - x**4/8 - x**5/15 + O(x**6) |
|
|
|
>>> f1.compose(f2, x).truncate(8) |
|
1 + x + x**2/2 - x**4/8 - x**5/15 - x**6/240 + x**7/90 + O(x**8) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.functions.combinatorial.numbers.bell |
|
sympy.series.formal.FormalPowerSeriesCompose |
|
|
|
References |
|
========== |
|
|
|
.. [1] Comtet, Louis: Advanced combinatorics; the art of finite and infinite expansions. Reidel, 1974. |
|
|
|
""" |
|
|
|
if n is None: |
|
return iter(self) |
|
|
|
other = sympify(other) |
|
|
|
if not isinstance(other, FormalPowerSeries): |
|
raise ValueError("Both series should be an instance of FormalPowerSeries" |
|
" class.") |
|
|
|
if self.dir != other.dir: |
|
raise ValueError("Both series should be calculated from the" |
|
" same direction.") |
|
elif self.x0 != other.x0: |
|
raise ValueError("Both series should be calculated about the" |
|
" same point.") |
|
|
|
elif self.x != other.x: |
|
raise ValueError("Both series should have the same symbol.") |
|
|
|
if other._eval_term(0).as_coeff_mul(other.x)[0] is not S.Zero: |
|
raise ValueError("The formal power series of the inner function should not have any " |
|
"constant coefficient term.") |
|
|
|
return FormalPowerSeriesCompose(self, other) |
|
|
|
def inverse(self, x=None, n=6): |
|
r""" |
|
Returns the truncated terms of the inverse of the formal power series, |
|
up to specified ``n``. |
|
|
|
Explanation |
|
=========== |
|
|
|
If ``f`` and ``g`` are two formal power series of two different functions, |
|
then the coefficient sequence ``ak`` of the composed formal power series ``fp`` |
|
will be as follows. |
|
|
|
.. math:: |
|
\sum\limits_{k=0}^{n} (-1)^{k} x_0^{-k-1} B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1}) |
|
|
|
Parameters |
|
========== |
|
|
|
n : Number, optional |
|
Specifies the order of the term up to which the polynomial should |
|
be truncated. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import fps, exp, cos |
|
>>> from sympy.abc import x |
|
>>> f1 = fps(exp(x)) |
|
>>> f2 = fps(cos(x)) |
|
|
|
>>> f1.inverse(x).truncate() |
|
1 - x + x**2/2 - x**3/6 + x**4/24 - x**5/120 + O(x**6) |
|
|
|
>>> f2.inverse(x).truncate(8) |
|
1 + x**2/2 + 5*x**4/24 + 61*x**6/720 + O(x**8) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.functions.combinatorial.numbers.bell |
|
sympy.series.formal.FormalPowerSeriesInverse |
|
|
|
References |
|
========== |
|
|
|
.. [1] Comtet, Louis: Advanced combinatorics; the art of finite and infinite expansions. Reidel, 1974. |
|
|
|
""" |
|
|
|
if n is None: |
|
return iter(self) |
|
|
|
if self._eval_term(0).is_zero: |
|
raise ValueError("Constant coefficient should exist for an inverse of a formal" |
|
" power series to exist.") |
|
|
|
return FormalPowerSeriesInverse(self) |
|
|
|
def __add__(self, other): |
|
other = sympify(other) |
|
|
|
if isinstance(other, FormalPowerSeries): |
|
if self.dir != other.dir: |
|
raise ValueError("Both series should be calculated from the" |
|
" same direction.") |
|
elif self.x0 != other.x0: |
|
raise ValueError("Both series should be calculated about the" |
|
" same point.") |
|
|
|
x, y = self.x, other.x |
|
f = self.function + other.function.subs(y, x) |
|
|
|
if self.x not in f.free_symbols: |
|
return f |
|
|
|
ak = self.ak + other.ak |
|
if self.ak.start > other.ak.start: |
|
seq = other.ak |
|
s, e = other.ak.start, self.ak.start |
|
else: |
|
seq = self.ak |
|
s, e = self.ak.start, other.ak.start |
|
save = Add(*[z[0]*z[1] for z in zip(seq[0:(e - s)], self.xk[s:e])]) |
|
ind = self.ind + other.ind + save |
|
|
|
return self.func(f, x, self.x0, self.dir, (ak, self.xk, ind)) |
|
|
|
elif not other.has(self.x): |
|
f = self.function + other |
|
ind = self.ind + other |
|
|
|
return self.func(f, self.x, self.x0, self.dir, |
|
(self.ak, self.xk, ind)) |
|
|
|
return Add(self, other) |
|
|
|
def __radd__(self, other): |
|
return self.__add__(other) |
|
|
|
def __neg__(self): |
|
return self.func(-self.function, self.x, self.x0, self.dir, |
|
(-self.ak, self.xk, -self.ind)) |
|
|
|
def __sub__(self, other): |
|
return self.__add__(-other) |
|
|
|
def __rsub__(self, other): |
|
return (-self).__add__(other) |
|
|
|
def __mul__(self, other): |
|
other = sympify(other) |
|
|
|
if other.has(self.x): |
|
return Mul(self, other) |
|
|
|
f = self.function * other |
|
ak = self.ak.coeff_mul(other) |
|
ind = self.ind * other |
|
|
|
return self.func(f, self.x, self.x0, self.dir, (ak, self.xk, ind)) |
|
|
|
def __rmul__(self, other): |
|
return self.__mul__(other) |
|
|
|
|
|
class FiniteFormalPowerSeries(FormalPowerSeries): |
|
"""Base Class for Product, Compose and Inverse classes""" |
|
|
|
def __init__(self, *args): |
|
pass |
|
|
|
@property |
|
def ffps(self): |
|
return self.args[0] |
|
|
|
@property |
|
def gfps(self): |
|
return self.args[1] |
|
|
|
@property |
|
def f(self): |
|
return self.ffps.function |
|
|
|
@property |
|
def g(self): |
|
return self.gfps.function |
|
|
|
@property |
|
def infinite(self): |
|
raise NotImplementedError("No infinite version for an object of" |
|
" FiniteFormalPowerSeries class.") |
|
|
|
def _eval_terms(self, n): |
|
raise NotImplementedError("(%s)._eval_terms()" % self) |
|
|
|
def _eval_term(self, pt): |
|
raise NotImplementedError("By the current logic, one can get terms" |
|
"upto a certain order, instead of getting term by term.") |
|
|
|
def polynomial(self, n): |
|
return self._eval_terms(n) |
|
|
|
def truncate(self, n=6): |
|
ffps = self.ffps |
|
pt_xk = ffps.xk.coeff(n) |
|
x, x0 = ffps.x, ffps.x0 |
|
|
|
return self.polynomial(n) + Order(pt_xk, (x, x0)) |
|
|
|
def _eval_derivative(self, x): |
|
raise NotImplementedError |
|
|
|
def integrate(self, x): |
|
raise NotImplementedError |
|
|
|
|
|
class FormalPowerSeriesProduct(FiniteFormalPowerSeries): |
|
"""Represents the product of two formal power series of two functions. |
|
|
|
Explanation |
|
=========== |
|
|
|
No computation is performed. Terms are calculated using a term by term logic, |
|
instead of a point by point logic. |
|
|
|
There are two differences between a :obj:`FormalPowerSeries` object and a |
|
:obj:`FormalPowerSeriesProduct` object. The first argument contains the two |
|
functions involved in the product. Also, the coefficient sequence contains |
|
both the coefficient sequence of the formal power series of the involved functions. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.FormalPowerSeries |
|
sympy.series.formal.FiniteFormalPowerSeries |
|
|
|
""" |
|
|
|
def __init__(self, *args): |
|
ffps, gfps = self.ffps, self.gfps |
|
|
|
k = ffps.ak.variables[0] |
|
self.coeff1 = sequence(ffps.ak.formula, (k, 0, oo)) |
|
|
|
k = gfps.ak.variables[0] |
|
self.coeff2 = sequence(gfps.ak.formula, (k, 0, oo)) |
|
|
|
@property |
|
def function(self): |
|
"""Function of the product of two formal power series.""" |
|
return self.f * self.g |
|
|
|
def _eval_terms(self, n): |
|
""" |
|
Returns the first ``n`` terms of the product formal power series. |
|
Term by term logic is implemented here. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import fps, sin, exp |
|
>>> from sympy.abc import x |
|
>>> f1 = fps(sin(x)) |
|
>>> f2 = fps(exp(x)) |
|
>>> fprod = f1.product(f2, x) |
|
|
|
>>> fprod._eval_terms(4) |
|
x**3/3 + x**2 + x |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.FormalPowerSeries.product |
|
|
|
""" |
|
coeff1, coeff2 = self.coeff1, self.coeff2 |
|
|
|
aks = convolution(coeff1[:n], coeff2[:n]) |
|
|
|
terms = [] |
|
for i in range(0, n): |
|
terms.append(aks[i] * self.ffps.xk.coeff(i)) |
|
|
|
return Add(*terms) |
|
|
|
|
|
class FormalPowerSeriesCompose(FiniteFormalPowerSeries): |
|
""" |
|
Represents the composed formal power series of two functions. |
|
|
|
Explanation |
|
=========== |
|
|
|
No computation is performed. Terms are calculated using a term by term logic, |
|
instead of a point by point logic. |
|
|
|
There are two differences between a :obj:`FormalPowerSeries` object and a |
|
:obj:`FormalPowerSeriesCompose` object. The first argument contains the outer |
|
function and the inner function involved in the omposition. Also, the |
|
coefficient sequence contains the generic sequence which is to be multiplied |
|
by a custom ``bell_seq`` finite sequence. The finite terms will then be added up to |
|
get the final terms. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.FormalPowerSeries |
|
sympy.series.formal.FiniteFormalPowerSeries |
|
|
|
""" |
|
|
|
@property |
|
def function(self): |
|
"""Function for the composed formal power series.""" |
|
f, g, x = self.f, self.g, self.ffps.x |
|
return f.subs(x, g) |
|
|
|
def _eval_terms(self, n): |
|
""" |
|
Returns the first `n` terms of the composed formal power series. |
|
Term by term logic is implemented here. |
|
|
|
Explanation |
|
=========== |
|
|
|
The coefficient sequence of the :obj:`FormalPowerSeriesCompose` object is the generic sequence. |
|
It is multiplied by ``bell_seq`` to get a sequence, whose terms are added up to get |
|
the final terms for the polynomial. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import fps, sin, exp |
|
>>> from sympy.abc import x |
|
>>> f1 = fps(exp(x)) |
|
>>> f2 = fps(sin(x)) |
|
>>> fcomp = f1.compose(f2, x) |
|
|
|
>>> fcomp._eval_terms(6) |
|
-x**5/15 - x**4/8 + x**2/2 + x + 1 |
|
|
|
>>> fcomp._eval_terms(8) |
|
x**7/90 - x**6/240 - x**5/15 - x**4/8 + x**2/2 + x + 1 |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.FormalPowerSeries.compose |
|
sympy.series.formal.FormalPowerSeries.coeff_bell |
|
|
|
""" |
|
|
|
ffps, gfps = self.ffps, self.gfps |
|
terms = [ffps.zero_coeff()] |
|
|
|
for i in range(1, n): |
|
bell_seq = gfps.coeff_bell(i) |
|
seq = (ffps.bell_coeff_seq * bell_seq) |
|
terms.append(Add(*(seq[:i])) / ffps.fact_seq[i-1] * ffps.xk.coeff(i)) |
|
|
|
return Add(*terms) |
|
|
|
|
|
class FormalPowerSeriesInverse(FiniteFormalPowerSeries): |
|
""" |
|
Represents the Inverse of a formal power series. |
|
|
|
Explanation |
|
=========== |
|
|
|
No computation is performed. Terms are calculated using a term by term logic, |
|
instead of a point by point logic. |
|
|
|
There is a single difference between a :obj:`FormalPowerSeries` object and a |
|
:obj:`FormalPowerSeriesInverse` object. The coefficient sequence contains the |
|
generic sequence which is to be multiplied by a custom ``bell_seq`` finite sequence. |
|
The finite terms will then be added up to get the final terms. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.FormalPowerSeries |
|
sympy.series.formal.FiniteFormalPowerSeries |
|
|
|
""" |
|
def __init__(self, *args): |
|
ffps = self.ffps |
|
k = ffps.xk.variables[0] |
|
|
|
inv = ffps.zero_coeff() |
|
inv_seq = sequence(inv ** (-(k + 1)), (k, 1, oo)) |
|
self.aux_seq = ffps.sign_seq * ffps.fact_seq * inv_seq |
|
|
|
@property |
|
def function(self): |
|
"""Function for the inverse of a formal power series.""" |
|
f = self.f |
|
return 1 / f |
|
|
|
@property |
|
def g(self): |
|
raise ValueError("Only one function is considered while performing" |
|
"inverse of a formal power series.") |
|
|
|
@property |
|
def gfps(self): |
|
raise ValueError("Only one function is considered while performing" |
|
"inverse of a formal power series.") |
|
|
|
def _eval_terms(self, n): |
|
""" |
|
Returns the first ``n`` terms of the composed formal power series. |
|
Term by term logic is implemented here. |
|
|
|
Explanation |
|
=========== |
|
|
|
The coefficient sequence of the `FormalPowerSeriesInverse` object is the generic sequence. |
|
It is multiplied by ``bell_seq`` to get a sequence, whose terms are added up to get |
|
the final terms for the polynomial. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import fps, exp, cos |
|
>>> from sympy.abc import x |
|
>>> f1 = fps(exp(x)) |
|
>>> f2 = fps(cos(x)) |
|
>>> finv1, finv2 = f1.inverse(), f2.inverse() |
|
|
|
>>> finv1._eval_terms(6) |
|
-x**5/120 + x**4/24 - x**3/6 + x**2/2 - x + 1 |
|
|
|
>>> finv2._eval_terms(8) |
|
61*x**6/720 + 5*x**4/24 + x**2/2 + 1 |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.FormalPowerSeries.inverse |
|
sympy.series.formal.FormalPowerSeries.coeff_bell |
|
|
|
""" |
|
ffps = self.ffps |
|
terms = [ffps.zero_coeff()] |
|
|
|
for i in range(1, n): |
|
bell_seq = ffps.coeff_bell(i) |
|
seq = (self.aux_seq * bell_seq) |
|
terms.append(Add(*(seq[:i])) / ffps.fact_seq[i-1] * ffps.xk.coeff(i)) |
|
|
|
return Add(*terms) |
|
|
|
|
|
def fps(f, x=None, x0=0, dir=1, hyper=True, order=4, rational=True, full=False): |
|
""" |
|
Generates Formal Power Series of ``f``. |
|
|
|
Explanation |
|
=========== |
|
|
|
Returns the formal series expansion of ``f`` around ``x = x0`` |
|
with respect to ``x`` in the form of a ``FormalPowerSeries`` object. |
|
|
|
Formal Power Series is represented using an explicit formula |
|
computed using different algorithms. |
|
|
|
See :func:`compute_fps` for the more details regarding the computation |
|
of formula. |
|
|
|
Parameters |
|
========== |
|
|
|
x : Symbol, optional |
|
If x is None and ``f`` is univariate, the univariate symbols will be |
|
supplied, otherwise an error will be raised. |
|
x0 : number, optional |
|
Point to perform series expansion about. Default is 0. |
|
dir : {1, -1, '+', '-'}, optional |
|
If dir is 1 or '+' the series is calculated from the right and |
|
for -1 or '-' the series is calculated from the left. For smooth |
|
functions this flag will not alter the results. Default is 1. |
|
hyper : {True, False}, optional |
|
Set hyper to False to skip the hypergeometric algorithm. |
|
By default it is set to False. |
|
order : int, optional |
|
Order of the derivative of ``f``, Default is 4. |
|
rational : {True, False}, optional |
|
Set rational to False to skip rational algorithm. By default it is set |
|
to True. |
|
full : {True, False}, optional |
|
Set full to True to increase the range of rational algorithm. |
|
See :func:`rational_algorithm` for details. By default it is set to |
|
False. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import fps, ln, atan, sin |
|
>>> from sympy.abc import x, n |
|
|
|
Rational Functions |
|
|
|
>>> fps(ln(1 + x)).truncate() |
|
x - x**2/2 + x**3/3 - x**4/4 + x**5/5 + O(x**6) |
|
|
|
>>> fps(atan(x), full=True).truncate() |
|
x - x**3/3 + x**5/5 + O(x**6) |
|
|
|
Symbolic Functions |
|
|
|
>>> fps(x**n*sin(x**2), x).truncate(8) |
|
-x**(n + 6)/6 + x**(n + 2) + O(x**(n + 8)) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.series.formal.FormalPowerSeries |
|
sympy.series.formal.compute_fps |
|
""" |
|
f = sympify(f) |
|
|
|
if x is None: |
|
free = f.free_symbols |
|
if len(free) == 1: |
|
x = free.pop() |
|
elif not free: |
|
return f |
|
else: |
|
raise NotImplementedError("multivariate formal power series") |
|
|
|
result = compute_fps(f, x, x0, dir, hyper, order, rational, full) |
|
|
|
if result is None: |
|
return f |
|
|
|
return FormalPowerSeries(f, x, x0, dir, result) |
|
|