Spaces:
Sleeping
Sleeping
""" Integral Transforms """ | |
from functools import reduce, wraps | |
from itertools import repeat | |
from sympy.core import S, pi | |
from sympy.core.add import Add | |
from sympy.core.function import ( | |
AppliedUndef, count_ops, expand, expand_mul, Function) | |
from sympy.core.mul import Mul | |
from sympy.core.intfunc import igcd, ilcm | |
from sympy.core.sorting import default_sort_key | |
from sympy.core.symbol import Dummy | |
from sympy.core.traversal import postorder_traversal | |
from sympy.functions.combinatorial.factorials import factorial, rf | |
from sympy.functions.elementary.complexes import re, arg, Abs | |
from sympy.functions.elementary.exponential import exp, exp_polar | |
from sympy.functions.elementary.hyperbolic import cosh, coth, sinh, tanh | |
from sympy.functions.elementary.integers import ceiling | |
from sympy.functions.elementary.miscellaneous import Max, Min, sqrt | |
from sympy.functions.elementary.piecewise import piecewise_fold | |
from sympy.functions.elementary.trigonometric import cos, cot, sin, tan | |
from sympy.functions.special.bessel import besselj | |
from sympy.functions.special.delta_functions import Heaviside | |
from sympy.functions.special.gamma_functions import gamma | |
from sympy.functions.special.hyper import meijerg | |
from sympy.integrals import integrate, Integral | |
from sympy.integrals.meijerint import _dummy | |
from sympy.logic.boolalg import to_cnf, conjuncts, disjuncts, Or, And | |
from sympy.polys.polyroots import roots | |
from sympy.polys.polytools import factor, Poly | |
from sympy.polys.rootoftools import CRootOf | |
from sympy.utilities.iterables import iterable | |
from sympy.utilities.misc import debug | |
########################################################################## | |
# Helpers / Utilities | |
########################################################################## | |
class IntegralTransformError(NotImplementedError): | |
""" | |
Exception raised in relation to problems computing transforms. | |
Explanation | |
=========== | |
This class is mostly used internally; if integrals cannot be computed | |
objects representing unevaluated transforms are usually returned. | |
The hint ``needeval=True`` can be used to disable returning transform | |
objects, and instead raise this exception if an integral cannot be | |
computed. | |
""" | |
def __init__(self, transform, function, msg): | |
super().__init__( | |
"%s Transform could not be computed: %s." % (transform, msg)) | |
self.function = function | |
class IntegralTransform(Function): | |
""" | |
Base class for integral transforms. | |
Explanation | |
=========== | |
This class represents unevaluated transforms. | |
To implement a concrete transform, derive from this class and implement | |
the ``_compute_transform(f, x, s, **hints)`` and ``_as_integral(f, x, s)`` | |
functions. If the transform cannot be computed, raise :obj:`IntegralTransformError`. | |
Also set ``cls._name``. For instance, | |
>>> from sympy import LaplaceTransform | |
>>> LaplaceTransform._name | |
'Laplace' | |
Implement ``self._collapse_extra`` if your function returns more than just a | |
number and possibly a convergence condition. | |
""" | |
def function(self): | |
""" The function to be transformed. """ | |
return self.args[0] | |
def function_variable(self): | |
""" The dependent variable of the function to be transformed. """ | |
return self.args[1] | |
def transform_variable(self): | |
""" The independent transform variable. """ | |
return self.args[2] | |
def free_symbols(self): | |
""" | |
This method returns the symbols that will exist when the transform | |
is evaluated. | |
""" | |
return self.function.free_symbols.union({self.transform_variable}) \ | |
- {self.function_variable} | |
def _compute_transform(self, f, x, s, **hints): | |
raise NotImplementedError | |
def _as_integral(self, f, x, s): | |
raise NotImplementedError | |
def _collapse_extra(self, extra): | |
cond = And(*extra) | |
if cond == False: | |
raise IntegralTransformError(self.__class__.name, None, '') | |
return cond | |
def _try_directly(self, **hints): | |
T = None | |
try_directly = not any(func.has(self.function_variable) | |
for func in self.function.atoms(AppliedUndef)) | |
if try_directly: | |
try: | |
T = self._compute_transform(self.function, | |
self.function_variable, self.transform_variable, **hints) | |
except IntegralTransformError: | |
debug('[IT _try ] Caught IntegralTransformError, returns None') | |
T = None | |
fn = self.function | |
if not fn.is_Add: | |
fn = expand_mul(fn) | |
return fn, T | |
def doit(self, **hints): | |
""" | |
Try to evaluate the transform in closed form. | |
Explanation | |
=========== | |
This general function handles linearity, but apart from that leaves | |
pretty much everything to _compute_transform. | |
Standard hints are the following: | |
- ``simplify``: whether or not to simplify the result | |
- ``noconds``: if True, do not return convergence conditions | |
- ``needeval``: if True, raise IntegralTransformError instead of | |
returning IntegralTransform objects | |
The default values of these hints depend on the concrete transform, | |
usually the default is | |
``(simplify, noconds, needeval) = (True, False, False)``. | |
""" | |
needeval = hints.pop('needeval', False) | |
simplify = hints.pop('simplify', True) | |
hints['simplify'] = simplify | |
fn, T = self._try_directly(**hints) | |
if T is not None: | |
return T | |
if fn.is_Add: | |
hints['needeval'] = needeval | |
res = [self.__class__(*([x] + list(self.args[1:]))).doit(**hints) | |
for x in fn.args] | |
extra = [] | |
ress = [] | |
for x in res: | |
if not isinstance(x, tuple): | |
x = [x] | |
ress.append(x[0]) | |
if len(x) == 2: | |
# only a condition | |
extra.append(x[1]) | |
elif len(x) > 2: | |
# some region parameters and a condition (Mellin, Laplace) | |
extra += [x[1:]] | |
if simplify==True: | |
res = Add(*ress).simplify() | |
else: | |
res = Add(*ress) | |
if not extra: | |
return res | |
try: | |
extra = self._collapse_extra(extra) | |
if iterable(extra): | |
return (res,) + tuple(extra) | |
else: | |
return (res, extra) | |
except IntegralTransformError: | |
pass | |
if needeval: | |
raise IntegralTransformError( | |
self.__class__._name, self.function, 'needeval') | |
# TODO handle derivatives etc | |
# pull out constant coefficients | |
coeff, rest = fn.as_coeff_mul(self.function_variable) | |
return coeff*self.__class__(*([Mul(*rest)] + list(self.args[1:]))) | |
def as_integral(self): | |
return self._as_integral(self.function, self.function_variable, | |
self.transform_variable) | |
def _eval_rewrite_as_Integral(self, *args, **kwargs): | |
return self.as_integral | |
def _simplify(expr, doit): | |
if doit: | |
from sympy.simplify import simplify | |
from sympy.simplify.powsimp import powdenest | |
return simplify(powdenest(piecewise_fold(expr), polar=True)) | |
return expr | |
def _noconds_(default): | |
""" | |
This is a decorator generator for dropping convergence conditions. | |
Explanation | |
=========== | |
Suppose you define a function ``transform(*args)`` which returns a tuple of | |
the form ``(result, cond1, cond2, ...)``. | |
Decorating it ``@_noconds_(default)`` will add a new keyword argument | |
``noconds`` to it. If ``noconds=True``, the return value will be altered to | |
be only ``result``, whereas if ``noconds=False`` the return value will not | |
be altered. | |
The default value of the ``noconds`` keyword will be ``default`` (i.e. the | |
argument of this function). | |
""" | |
def make_wrapper(func): | |
def wrapper(*args, noconds=default, **kwargs): | |
res = func(*args, **kwargs) | |
if noconds: | |
return res[0] | |
return res | |
return wrapper | |
return make_wrapper | |
_noconds = _noconds_(False) | |
########################################################################## | |
# Mellin Transform | |
########################################################################## | |
def _default_integrator(f, x): | |
return integrate(f, (x, S.Zero, S.Infinity)) | |
def _mellin_transform(f, x, s_, integrator=_default_integrator, simplify=True): | |
""" Backend function to compute Mellin transforms. """ | |
# We use a fresh dummy, because assumptions on s might drop conditions on | |
# convergence of the integral. | |
s = _dummy('s', 'mellin-transform', f) | |
F = integrator(x**(s - 1) * f, x) | |
if not F.has(Integral): | |
return _simplify(F.subs(s, s_), simplify), (S.NegativeInfinity, S.Infinity), S.true | |
if not F.is_Piecewise: # XXX can this work if integration gives continuous result now? | |
raise IntegralTransformError('Mellin', f, 'could not compute integral') | |
F, cond = F.args[0] | |
if F.has(Integral): | |
raise IntegralTransformError( | |
'Mellin', f, 'integral in unexpected form') | |
def process_conds(cond): | |
""" | |
Turn ``cond`` into a strip (a, b), and auxiliary conditions. | |
""" | |
from sympy.solvers.inequalities import _solve_inequality | |
a = S.NegativeInfinity | |
b = S.Infinity | |
aux = S.true | |
conds = conjuncts(to_cnf(cond)) | |
t = Dummy('t', real=True) | |
for c in conds: | |
a_ = S.Infinity | |
b_ = S.NegativeInfinity | |
aux_ = [] | |
for d in disjuncts(c): | |
d_ = d.replace( | |
re, lambda x: x.as_real_imag()[0]).subs(re(s), t) | |
if not d.is_Relational or \ | |
d.rel_op in ('==', '!=') \ | |
or d_.has(s) or not d_.has(t): | |
aux_ += [d] | |
continue | |
soln = _solve_inequality(d_, t) | |
if not soln.is_Relational or \ | |
soln.rel_op in ('==', '!='): | |
aux_ += [d] | |
continue | |
if soln.lts == t: | |
b_ = Max(soln.gts, b_) | |
else: | |
a_ = Min(soln.lts, a_) | |
if a_ is not S.Infinity and a_ != b: | |
a = Max(a_, a) | |
elif b_ is not S.NegativeInfinity and b_ != a: | |
b = Min(b_, b) | |
else: | |
aux = And(aux, Or(*aux_)) | |
return a, b, aux | |
conds = [process_conds(c) for c in disjuncts(cond)] | |
conds = [x for x in conds if x[2] != False] | |
conds.sort(key=lambda x: (x[0] - x[1], count_ops(x[2]))) | |
if not conds: | |
raise IntegralTransformError('Mellin', f, 'no convergence found') | |
a, b, aux = conds[0] | |
return _simplify(F.subs(s, s_), simplify), (a, b), aux | |
class MellinTransform(IntegralTransform): | |
""" | |
Class representing unevaluated Mellin transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute Mellin transforms, see the :func:`mellin_transform` | |
docstring. | |
""" | |
_name = 'Mellin' | |
def _compute_transform(self, f, x, s, **hints): | |
return _mellin_transform(f, x, s, **hints) | |
def _as_integral(self, f, x, s): | |
return Integral(f*x**(s - 1), (x, S.Zero, S.Infinity)) | |
def _collapse_extra(self, extra): | |
a = [] | |
b = [] | |
cond = [] | |
for (sa, sb), c in extra: | |
a += [sa] | |
b += [sb] | |
cond += [c] | |
res = (Max(*a), Min(*b)), And(*cond) | |
if (res[0][0] >= res[0][1]) == True or res[1] == False: | |
raise IntegralTransformError( | |
'Mellin', None, 'no combined convergence.') | |
return res | |
def mellin_transform(f, x, s, **hints): | |
r""" | |
Compute the Mellin transform `F(s)` of `f(x)`, | |
.. math :: F(s) = \int_0^\infty x^{s-1} f(x) \mathrm{d}x. | |
For all "sensible" functions, this converges absolutely in a strip | |
`a < \operatorname{Re}(s) < b`. | |
Explanation | |
=========== | |
The Mellin transform is related via change of variables to the Fourier | |
transform, and also to the (bilateral) Laplace transform. | |
This function returns ``(F, (a, b), cond)`` | |
where ``F`` is the Mellin transform of ``f``, ``(a, b)`` is the fundamental strip | |
(as above), and ``cond`` are auxiliary convergence conditions. | |
If the integral cannot be computed in closed form, this function returns | |
an unevaluated :class:`MellinTransform` object. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. If ``noconds=False``, | |
then only `F` will be returned (i.e. not ``cond``, and also not the strip | |
``(a, b)``). | |
Examples | |
======== | |
>>> from sympy import mellin_transform, exp | |
>>> from sympy.abc import x, s | |
>>> mellin_transform(exp(-x), x, s) | |
(gamma(s), (0, oo), True) | |
See Also | |
======== | |
inverse_mellin_transform, laplace_transform, fourier_transform | |
hankel_transform, inverse_hankel_transform | |
""" | |
return MellinTransform(f, x, s).doit(**hints) | |
def _rewrite_sin(m_n, s, a, b): | |
""" | |
Re-write the sine function ``sin(m*s + n)`` as gamma functions, compatible | |
with the strip (a, b). | |
Return ``(gamma1, gamma2, fac)`` so that ``f == fac/(gamma1 * gamma2)``. | |
Examples | |
======== | |
>>> from sympy.integrals.transforms import _rewrite_sin | |
>>> from sympy import pi, S | |
>>> from sympy.abc import s | |
>>> _rewrite_sin((pi, 0), s, 0, 1) | |
(gamma(s), gamma(1 - s), pi) | |
>>> _rewrite_sin((pi, 0), s, 1, 0) | |
(gamma(s - 1), gamma(2 - s), -pi) | |
>>> _rewrite_sin((pi, 0), s, -1, 0) | |
(gamma(s + 1), gamma(-s), -pi) | |
>>> _rewrite_sin((pi, pi/2), s, S(1)/2, S(3)/2) | |
(gamma(s - 1/2), gamma(3/2 - s), -pi) | |
>>> _rewrite_sin((pi, pi), s, 0, 1) | |
(gamma(s), gamma(1 - s), -pi) | |
>>> _rewrite_sin((2*pi, 0), s, 0, S(1)/2) | |
(gamma(2*s), gamma(1 - 2*s), pi) | |
>>> _rewrite_sin((2*pi, 0), s, S(1)/2, 1) | |
(gamma(2*s - 1), gamma(2 - 2*s), -pi) | |
""" | |
# (This is a separate function because it is moderately complicated, | |
# and I want to doctest it.) | |
# We want to use pi/sin(pi*x) = gamma(x)*gamma(1-x). | |
# But there is one comlication: the gamma functions determine the | |
# inegration contour in the definition of the G-function. Usually | |
# it would not matter if this is slightly shifted, unless this way | |
# we create an undefined function! | |
# So we try to write this in such a way that the gammas are | |
# eminently on the right side of the strip. | |
m, n = m_n | |
m = expand_mul(m/pi) | |
n = expand_mul(n/pi) | |
r = ceiling(-m*a - n.as_real_imag()[0]) # Don't use re(n), does not expand | |
return gamma(m*s + n + r), gamma(1 - n - r - m*s), (-1)**r*pi | |
class MellinTransformStripError(ValueError): | |
""" | |
Exception raised by _rewrite_gamma. Mainly for internal use. | |
""" | |
pass | |
def _rewrite_gamma(f, s, a, b): | |
""" | |
Try to rewrite the product f(s) as a product of gamma functions, | |
so that the inverse Mellin transform of f can be expressed as a meijer | |
G function. | |
Explanation | |
=========== | |
Return (an, ap), (bm, bq), arg, exp, fac such that | |
G((an, ap), (bm, bq), arg/z**exp)*fac is the inverse Mellin transform of f(s). | |
Raises IntegralTransformError or MellinTransformStripError on failure. | |
It is asserted that f has no poles in the fundamental strip designated by | |
(a, b). One of a and b is allowed to be None. The fundamental strip is | |
important, because it determines the inversion contour. | |
This function can handle exponentials, linear factors, trigonometric | |
functions. | |
This is a helper function for inverse_mellin_transform that will not | |
attempt any transformations on f. | |
Examples | |
======== | |
>>> from sympy.integrals.transforms import _rewrite_gamma | |
>>> from sympy.abc import s | |
>>> from sympy import oo | |
>>> _rewrite_gamma(s*(s+3)*(s-1), s, -oo, oo) | |
(([], [-3, 0, 1]), ([-2, 1, 2], []), 1, 1, -1) | |
>>> _rewrite_gamma((s-1)**2, s, -oo, oo) | |
(([], [1, 1]), ([2, 2], []), 1, 1, 1) | |
Importance of the fundamental strip: | |
>>> _rewrite_gamma(1/s, s, 0, oo) | |
(([1], []), ([], [0]), 1, 1, 1) | |
>>> _rewrite_gamma(1/s, s, None, oo) | |
(([1], []), ([], [0]), 1, 1, 1) | |
>>> _rewrite_gamma(1/s, s, 0, None) | |
(([1], []), ([], [0]), 1, 1, 1) | |
>>> _rewrite_gamma(1/s, s, -oo, 0) | |
(([], [1]), ([0], []), 1, 1, -1) | |
>>> _rewrite_gamma(1/s, s, None, 0) | |
(([], [1]), ([0], []), 1, 1, -1) | |
>>> _rewrite_gamma(1/s, s, -oo, None) | |
(([], [1]), ([0], []), 1, 1, -1) | |
>>> _rewrite_gamma(2**(-s+3), s, -oo, oo) | |
(([], []), ([], []), 1/2, 1, 8) | |
""" | |
# Our strategy will be as follows: | |
# 1) Guess a constant c such that the inversion integral should be | |
# performed wrt s'=c*s (instead of plain s). Write s for s'. | |
# 2) Process all factors, rewrite them independently as gamma functions in | |
# argument s, or exponentials of s. | |
# 3) Try to transform all gamma functions s.t. they have argument | |
# a+s or a-s. | |
# 4) Check that the resulting G function parameters are valid. | |
# 5) Combine all the exponentials. | |
a_, b_ = S([a, b]) | |
def left(c, is_numer): | |
""" | |
Decide whether pole at c lies to the left of the fundamental strip. | |
""" | |
# heuristically, this is the best chance for us to solve the inequalities | |
c = expand(re(c)) | |
if a_ is None and b_ is S.Infinity: | |
return True | |
if a_ is None: | |
return c < b_ | |
if b_ is None: | |
return c <= a_ | |
if (c >= b_) == True: | |
return False | |
if (c <= a_) == True: | |
return True | |
if is_numer: | |
return None | |
if a_.free_symbols or b_.free_symbols or c.free_symbols: | |
return None # XXX | |
#raise IntegralTransformError('Inverse Mellin', f, | |
# 'Could not determine position of singularity %s' | |
# ' relative to fundamental strip' % c) | |
raise MellinTransformStripError('Pole inside critical strip?') | |
# 1) | |
s_multipliers = [] | |
for g in f.atoms(gamma): | |
if not g.has(s): | |
continue | |
arg = g.args[0] | |
if arg.is_Add: | |
arg = arg.as_independent(s)[1] | |
coeff, _ = arg.as_coeff_mul(s) | |
s_multipliers += [coeff] | |
for g in f.atoms(sin, cos, tan, cot): | |
if not g.has(s): | |
continue | |
arg = g.args[0] | |
if arg.is_Add: | |
arg = arg.as_independent(s)[1] | |
coeff, _ = arg.as_coeff_mul(s) | |
s_multipliers += [coeff/pi] | |
s_multipliers = [Abs(x) if x.is_extended_real else x for x in s_multipliers] | |
common_coefficient = S.One | |
for x in s_multipliers: | |
if not x.is_Rational: | |
common_coefficient = x | |
break | |
s_multipliers = [x/common_coefficient for x in s_multipliers] | |
if not (all(x.is_Rational for x in s_multipliers) and | |
common_coefficient.is_extended_real): | |
raise IntegralTransformError("Gamma", None, "Nonrational multiplier") | |
s_multiplier = common_coefficient/reduce(ilcm, [S(x.q) | |
for x in s_multipliers], S.One) | |
if s_multiplier == common_coefficient: | |
if len(s_multipliers) == 0: | |
s_multiplier = common_coefficient | |
else: | |
s_multiplier = common_coefficient \ | |
*reduce(igcd, [S(x.p) for x in s_multipliers]) | |
f = f.subs(s, s/s_multiplier) | |
fac = S.One/s_multiplier | |
exponent = S.One/s_multiplier | |
if a_ is not None: | |
a_ *= s_multiplier | |
if b_ is not None: | |
b_ *= s_multiplier | |
# 2) | |
numer, denom = f.as_numer_denom() | |
numer = Mul.make_args(numer) | |
denom = Mul.make_args(denom) | |
args = list(zip(numer, repeat(True))) + list(zip(denom, repeat(False))) | |
facs = [] | |
dfacs = [] | |
# *_gammas will contain pairs (a, c) representing Gamma(a*s + c) | |
numer_gammas = [] | |
denom_gammas = [] | |
# exponentials will contain bases for exponentials of s | |
exponentials = [] | |
def exception(fact): | |
return IntegralTransformError("Inverse Mellin", f, "Unrecognised form '%s'." % fact) | |
while args: | |
fact, is_numer = args.pop() | |
if is_numer: | |
ugammas, lgammas = numer_gammas, denom_gammas | |
ufacs = facs | |
else: | |
ugammas, lgammas = denom_gammas, numer_gammas | |
ufacs = dfacs | |
def linear_arg(arg): | |
""" Test if arg is of form a*s+b, raise exception if not. """ | |
if not arg.is_polynomial(s): | |
raise exception(fact) | |
p = Poly(arg, s) | |
if p.degree() != 1: | |
raise exception(fact) | |
return p.all_coeffs() | |
# constants | |
if not fact.has(s): | |
ufacs += [fact] | |
# exponentials | |
elif fact.is_Pow or isinstance(fact, exp): | |
if fact.is_Pow: | |
base = fact.base | |
exp_ = fact.exp | |
else: | |
base = exp_polar(1) | |
exp_ = fact.exp | |
if exp_.is_Integer: | |
cond = is_numer | |
if exp_ < 0: | |
cond = not cond | |
args += [(base, cond)]*Abs(exp_) | |
continue | |
elif not base.has(s): | |
a, b = linear_arg(exp_) | |
if not is_numer: | |
base = 1/base | |
exponentials += [base**a] | |
facs += [base**b] | |
else: | |
raise exception(fact) | |
# linear factors | |
elif fact.is_polynomial(s): | |
p = Poly(fact, s) | |
if p.degree() != 1: | |
# We completely factor the poly. For this we need the roots. | |
# Now roots() only works in some cases (low degree), and CRootOf | |
# only works without parameters. So try both... | |
coeff = p.LT()[1] | |
rs = roots(p, s) | |
if len(rs) != p.degree(): | |
rs = CRootOf.all_roots(p) | |
ufacs += [coeff] | |
args += [(s - c, is_numer) for c in rs] | |
continue | |
a, c = p.all_coeffs() | |
ufacs += [a] | |
c /= -a | |
# Now need to convert s - c | |
if left(c, is_numer): | |
ugammas += [(S.One, -c + 1)] | |
lgammas += [(S.One, -c)] | |
else: | |
ufacs += [-1] | |
ugammas += [(S.NegativeOne, c + 1)] | |
lgammas += [(S.NegativeOne, c)] | |
elif isinstance(fact, gamma): | |
a, b = linear_arg(fact.args[0]) | |
if is_numer: | |
if (a > 0 and (left(-b/a, is_numer) == False)) or \ | |
(a < 0 and (left(-b/a, is_numer) == True)): | |
raise NotImplementedError( | |
'Gammas partially over the strip.') | |
ugammas += [(a, b)] | |
elif isinstance(fact, sin): | |
# We try to re-write all trigs as gammas. This is not in | |
# general the best strategy, since sometimes this is impossible, | |
# but rewriting as exponentials would work. However trig functions | |
# in inverse mellin transforms usually all come from simplifying | |
# gamma terms, so this should work. | |
a = fact.args[0] | |
if is_numer: | |
# No problem with the poles. | |
gamma1, gamma2, fac_ = gamma(a/pi), gamma(1 - a/pi), pi | |
else: | |
gamma1, gamma2, fac_ = _rewrite_sin(linear_arg(a), s, a_, b_) | |
args += [(gamma1, not is_numer), (gamma2, not is_numer)] | |
ufacs += [fac_] | |
elif isinstance(fact, tan): | |
a = fact.args[0] | |
args += [(sin(a, evaluate=False), is_numer), | |
(sin(pi/2 - a, evaluate=False), not is_numer)] | |
elif isinstance(fact, cos): | |
a = fact.args[0] | |
args += [(sin(pi/2 - a, evaluate=False), is_numer)] | |
elif isinstance(fact, cot): | |
a = fact.args[0] | |
args += [(sin(pi/2 - a, evaluate=False), is_numer), | |
(sin(a, evaluate=False), not is_numer)] | |
else: | |
raise exception(fact) | |
fac *= Mul(*facs)/Mul(*dfacs) | |
# 3) | |
an, ap, bm, bq = [], [], [], [] | |
for gammas, plus, minus, is_numer in [(numer_gammas, an, bm, True), | |
(denom_gammas, bq, ap, False)]: | |
while gammas: | |
a, c = gammas.pop() | |
if a != -1 and a != +1: | |
# We use the gamma function multiplication theorem. | |
p = Abs(S(a)) | |
newa = a/p | |
newc = c/p | |
if not a.is_Integer: | |
raise TypeError("a is not an integer") | |
for k in range(p): | |
gammas += [(newa, newc + k/p)] | |
if is_numer: | |
fac *= (2*pi)**((1 - p)/2) * p**(c - S.Half) | |
exponentials += [p**a] | |
else: | |
fac /= (2*pi)**((1 - p)/2) * p**(c - S.Half) | |
exponentials += [p**(-a)] | |
continue | |
if a == +1: | |
plus.append(1 - c) | |
else: | |
minus.append(c) | |
# 4) | |
# TODO | |
# 5) | |
arg = Mul(*exponentials) | |
# for testability, sort the arguments | |
an.sort(key=default_sort_key) | |
ap.sort(key=default_sort_key) | |
bm.sort(key=default_sort_key) | |
bq.sort(key=default_sort_key) | |
return (an, ap), (bm, bq), arg, exponent, fac | |
def _inverse_mellin_transform(F, s, x_, strip, as_meijerg=False): | |
""" A helper for the real inverse_mellin_transform function, this one here | |
assumes x to be real and positive. """ | |
x = _dummy('t', 'inverse-mellin-transform', F, positive=True) | |
# Actually, we won't try integration at all. Instead we use the definition | |
# of the Meijer G function as a fairly general inverse mellin transform. | |
F = F.rewrite(gamma) | |
for g in [factor(F), expand_mul(F), expand(F)]: | |
if g.is_Add: | |
# do all terms separately | |
ress = [_inverse_mellin_transform(G, s, x, strip, as_meijerg, | |
noconds=False) | |
for G in g.args] | |
conds = [p[1] for p in ress] | |
ress = [p[0] for p in ress] | |
res = Add(*ress) | |
if not as_meijerg: | |
res = factor(res, gens=res.atoms(Heaviside)) | |
return res.subs(x, x_), And(*conds) | |
try: | |
a, b, C, e, fac = _rewrite_gamma(g, s, strip[0], strip[1]) | |
except IntegralTransformError: | |
continue | |
try: | |
G = meijerg(a, b, C/x**e) | |
except ValueError: | |
continue | |
if as_meijerg: | |
h = G | |
else: | |
try: | |
from sympy.simplify import hyperexpand | |
h = hyperexpand(G) | |
except NotImplementedError: | |
raise IntegralTransformError( | |
'Inverse Mellin', F, 'Could not calculate integral') | |
if h.is_Piecewise and len(h.args) == 3: | |
# XXX we break modularity here! | |
h = Heaviside(x - Abs(C))*h.args[0].args[0] \ | |
+ Heaviside(Abs(C) - x)*h.args[1].args[0] | |
# We must ensure that the integral along the line we want converges, | |
# and return that value. | |
# See [L], 5.2 | |
cond = [Abs(arg(G.argument)) < G.delta*pi] | |
# Note: we allow ">=" here, this corresponds to convergence if we let | |
# limits go to oo symmetrically. ">" corresponds to absolute convergence. | |
cond += [And(Or(len(G.ap) != len(G.bq), 0 >= re(G.nu) + 1), | |
Abs(arg(G.argument)) == G.delta*pi)] | |
cond = Or(*cond) | |
if cond == False: | |
raise IntegralTransformError( | |
'Inverse Mellin', F, 'does not converge') | |
return (h*fac).subs(x, x_), cond | |
raise IntegralTransformError('Inverse Mellin', F, '') | |
_allowed = None | |
class InverseMellinTransform(IntegralTransform): | |
""" | |
Class representing unevaluated inverse Mellin transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute inverse Mellin transforms, see the | |
:func:`inverse_mellin_transform` docstring. | |
""" | |
_name = 'Inverse Mellin' | |
_none_sentinel = Dummy('None') | |
_c = Dummy('c') | |
def __new__(cls, F, s, x, a, b, **opts): | |
if a is None: | |
a = InverseMellinTransform._none_sentinel | |
if b is None: | |
b = InverseMellinTransform._none_sentinel | |
return IntegralTransform.__new__(cls, F, s, x, a, b, **opts) | |
def fundamental_strip(self): | |
a, b = self.args[3], self.args[4] | |
if a is InverseMellinTransform._none_sentinel: | |
a = None | |
if b is InverseMellinTransform._none_sentinel: | |
b = None | |
return a, b | |
def _compute_transform(self, F, s, x, **hints): | |
# IntegralTransform's doit will cause this hint to exist, but | |
# InverseMellinTransform should ignore it | |
hints.pop('simplify', True) | |
global _allowed | |
if _allowed is None: | |
_allowed = { | |
exp, gamma, sin, cos, tan, cot, cosh, sinh, tanh, coth, | |
factorial, rf} | |
for f in postorder_traversal(F): | |
if f.is_Function and f.has(s) and f.func not in _allowed: | |
raise IntegralTransformError('Inverse Mellin', F, | |
'Component %s not recognised.' % f) | |
strip = self.fundamental_strip | |
return _inverse_mellin_transform(F, s, x, strip, **hints) | |
def _as_integral(self, F, s, x): | |
c = self.__class__._c | |
return Integral(F*x**(-s), (s, c - S.ImaginaryUnit*S.Infinity, c + | |
S.ImaginaryUnit*S.Infinity))/(2*S.Pi*S.ImaginaryUnit) | |
def inverse_mellin_transform(F, s, x, strip, **hints): | |
r""" | |
Compute the inverse Mellin transform of `F(s)` over the fundamental | |
strip given by ``strip=(a, b)``. | |
Explanation | |
=========== | |
This can be defined as | |
.. math:: f(x) = \frac{1}{2\pi i} \int_{c - i\infty}^{c + i\infty} x^{-s} F(s) \mathrm{d}s, | |
for any `c` in the fundamental strip. Under certain regularity | |
conditions on `F` and/or `f`, | |
this recovers `f` from its Mellin transform `F` | |
(and vice versa), for positive real `x`. | |
One of `a` or `b` may be passed as ``None``; a suitable `c` will be | |
inferred. | |
If the integral cannot be computed in closed form, this function returns | |
an unevaluated :class:`InverseMellinTransform` object. | |
Note that this function will assume x to be positive and real, regardless | |
of the SymPy assumptions! | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Examples | |
======== | |
>>> from sympy import inverse_mellin_transform, oo, gamma | |
>>> from sympy.abc import x, s | |
>>> inverse_mellin_transform(gamma(s), s, x, (0, oo)) | |
exp(-x) | |
The fundamental strip matters: | |
>>> f = 1/(s**2 - 1) | |
>>> inverse_mellin_transform(f, s, x, (-oo, -1)) | |
x*(1 - 1/x**2)*Heaviside(x - 1)/2 | |
>>> inverse_mellin_transform(f, s, x, (-1, 1)) | |
-x*Heaviside(1 - x)/2 - Heaviside(x - 1)/(2*x) | |
>>> inverse_mellin_transform(f, s, x, (1, oo)) | |
(1/2 - x**2/2)*Heaviside(1 - x)/x | |
See Also | |
======== | |
mellin_transform | |
hankel_transform, inverse_hankel_transform | |
""" | |
return InverseMellinTransform(F, s, x, strip[0], strip[1]).doit(**hints) | |
########################################################################## | |
# Fourier Transform | |
########################################################################## | |
def _fourier_transform(f, x, k, a, b, name, simplify=True): | |
r""" | |
Compute a general Fourier-type transform | |
.. math:: | |
F(k) = a \int_{-\infty}^{\infty} e^{bixk} f(x)\, dx. | |
For suitable choice of *a* and *b*, this reduces to the standard Fourier | |
and inverse Fourier transforms. | |
""" | |
F = integrate(a*f*exp(b*S.ImaginaryUnit*x*k), (x, S.NegativeInfinity, S.Infinity)) | |
if not F.has(Integral): | |
return _simplify(F, simplify), S.true | |
integral_f = integrate(f, (x, S.NegativeInfinity, S.Infinity)) | |
if integral_f in (S.NegativeInfinity, S.Infinity, S.NaN) or integral_f.has(Integral): | |
raise IntegralTransformError(name, f, 'function not integrable on real axis') | |
if not F.is_Piecewise: | |
raise IntegralTransformError(name, f, 'could not compute integral') | |
F, cond = F.args[0] | |
if F.has(Integral): | |
raise IntegralTransformError(name, f, 'integral in unexpected form') | |
return _simplify(F, simplify), cond | |
class FourierTypeTransform(IntegralTransform): | |
""" Base class for Fourier transforms.""" | |
def a(self): | |
raise NotImplementedError( | |
"Class %s must implement a(self) but does not" % self.__class__) | |
def b(self): | |
raise NotImplementedError( | |
"Class %s must implement b(self) but does not" % self.__class__) | |
def _compute_transform(self, f, x, k, **hints): | |
return _fourier_transform(f, x, k, | |
self.a(), self.b(), | |
self.__class__._name, **hints) | |
def _as_integral(self, f, x, k): | |
a = self.a() | |
b = self.b() | |
return Integral(a*f*exp(b*S.ImaginaryUnit*x*k), (x, S.NegativeInfinity, S.Infinity)) | |
class FourierTransform(FourierTypeTransform): | |
""" | |
Class representing unevaluated Fourier transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute Fourier transforms, see the :func:`fourier_transform` | |
docstring. | |
""" | |
_name = 'Fourier' | |
def a(self): | |
return 1 | |
def b(self): | |
return -2*S.Pi | |
def fourier_transform(f, x, k, **hints): | |
r""" | |
Compute the unitary, ordinary-frequency Fourier transform of ``f``, defined | |
as | |
.. math:: F(k) = \int_{-\infty}^\infty f(x) e^{-2\pi i x k} \mathrm{d} x. | |
Explanation | |
=========== | |
If the transform cannot be computed in closed form, this | |
function returns an unevaluated :class:`FourierTransform` object. | |
For other Fourier transform conventions, see the function | |
:func:`sympy.integrals.transforms._fourier_transform`. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Note that for this transform, by default ``noconds=True``. | |
Examples | |
======== | |
>>> from sympy import fourier_transform, exp | |
>>> from sympy.abc import x, k | |
>>> fourier_transform(exp(-x**2), x, k) | |
sqrt(pi)*exp(-pi**2*k**2) | |
>>> fourier_transform(exp(-x**2), x, k, noconds=False) | |
(sqrt(pi)*exp(-pi**2*k**2), True) | |
See Also | |
======== | |
inverse_fourier_transform | |
sine_transform, inverse_sine_transform | |
cosine_transform, inverse_cosine_transform | |
hankel_transform, inverse_hankel_transform | |
mellin_transform, laplace_transform | |
""" | |
return FourierTransform(f, x, k).doit(**hints) | |
class InverseFourierTransform(FourierTypeTransform): | |
""" | |
Class representing unevaluated inverse Fourier transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute inverse Fourier transforms, see the | |
:func:`inverse_fourier_transform` docstring. | |
""" | |
_name = 'Inverse Fourier' | |
def a(self): | |
return 1 | |
def b(self): | |
return 2*S.Pi | |
def inverse_fourier_transform(F, k, x, **hints): | |
r""" | |
Compute the unitary, ordinary-frequency inverse Fourier transform of `F`, | |
defined as | |
.. math:: f(x) = \int_{-\infty}^\infty F(k) e^{2\pi i x k} \mathrm{d} k. | |
Explanation | |
=========== | |
If the transform cannot be computed in closed form, this | |
function returns an unevaluated :class:`InverseFourierTransform` object. | |
For other Fourier transform conventions, see the function | |
:func:`sympy.integrals.transforms._fourier_transform`. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Note that for this transform, by default ``noconds=True``. | |
Examples | |
======== | |
>>> from sympy import inverse_fourier_transform, exp, sqrt, pi | |
>>> from sympy.abc import x, k | |
>>> inverse_fourier_transform(sqrt(pi)*exp(-(pi*k)**2), k, x) | |
exp(-x**2) | |
>>> inverse_fourier_transform(sqrt(pi)*exp(-(pi*k)**2), k, x, noconds=False) | |
(exp(-x**2), True) | |
See Also | |
======== | |
fourier_transform | |
sine_transform, inverse_sine_transform | |
cosine_transform, inverse_cosine_transform | |
hankel_transform, inverse_hankel_transform | |
mellin_transform, laplace_transform | |
""" | |
return InverseFourierTransform(F, k, x).doit(**hints) | |
########################################################################## | |
# Fourier Sine and Cosine Transform | |
########################################################################## | |
def _sine_cosine_transform(f, x, k, a, b, K, name, simplify=True): | |
""" | |
Compute a general sine or cosine-type transform | |
F(k) = a int_0^oo b*sin(x*k) f(x) dx. | |
F(k) = a int_0^oo b*cos(x*k) f(x) dx. | |
For suitable choice of a and b, this reduces to the standard sine/cosine | |
and inverse sine/cosine transforms. | |
""" | |
F = integrate(a*f*K(b*x*k), (x, S.Zero, S.Infinity)) | |
if not F.has(Integral): | |
return _simplify(F, simplify), S.true | |
if not F.is_Piecewise: | |
raise IntegralTransformError(name, f, 'could not compute integral') | |
F, cond = F.args[0] | |
if F.has(Integral): | |
raise IntegralTransformError(name, f, 'integral in unexpected form') | |
return _simplify(F, simplify), cond | |
class SineCosineTypeTransform(IntegralTransform): | |
""" | |
Base class for sine and cosine transforms. | |
Specify cls._kern. | |
""" | |
def a(self): | |
raise NotImplementedError( | |
"Class %s must implement a(self) but does not" % self.__class__) | |
def b(self): | |
raise NotImplementedError( | |
"Class %s must implement b(self) but does not" % self.__class__) | |
def _compute_transform(self, f, x, k, **hints): | |
return _sine_cosine_transform(f, x, k, | |
self.a(), self.b(), | |
self.__class__._kern, | |
self.__class__._name, **hints) | |
def _as_integral(self, f, x, k): | |
a = self.a() | |
b = self.b() | |
K = self.__class__._kern | |
return Integral(a*f*K(b*x*k), (x, S.Zero, S.Infinity)) | |
class SineTransform(SineCosineTypeTransform): | |
""" | |
Class representing unevaluated sine transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute sine transforms, see the :func:`sine_transform` | |
docstring. | |
""" | |
_name = 'Sine' | |
_kern = sin | |
def a(self): | |
return sqrt(2)/sqrt(pi) | |
def b(self): | |
return S.One | |
def sine_transform(f, x, k, **hints): | |
r""" | |
Compute the unitary, ordinary-frequency sine transform of `f`, defined | |
as | |
.. math:: F(k) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty f(x) \sin(2\pi x k) \mathrm{d} x. | |
Explanation | |
=========== | |
If the transform cannot be computed in closed form, this | |
function returns an unevaluated :class:`SineTransform` object. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Note that for this transform, by default ``noconds=True``. | |
Examples | |
======== | |
>>> from sympy import sine_transform, exp | |
>>> from sympy.abc import x, k, a | |
>>> sine_transform(x*exp(-a*x**2), x, k) | |
sqrt(2)*k*exp(-k**2/(4*a))/(4*a**(3/2)) | |
>>> sine_transform(x**(-a), x, k) | |
2**(1/2 - a)*k**(a - 1)*gamma(1 - a/2)/gamma(a/2 + 1/2) | |
See Also | |
======== | |
fourier_transform, inverse_fourier_transform | |
inverse_sine_transform | |
cosine_transform, inverse_cosine_transform | |
hankel_transform, inverse_hankel_transform | |
mellin_transform, laplace_transform | |
""" | |
return SineTransform(f, x, k).doit(**hints) | |
class InverseSineTransform(SineCosineTypeTransform): | |
""" | |
Class representing unevaluated inverse sine transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute inverse sine transforms, see the | |
:func:`inverse_sine_transform` docstring. | |
""" | |
_name = 'Inverse Sine' | |
_kern = sin | |
def a(self): | |
return sqrt(2)/sqrt(pi) | |
def b(self): | |
return S.One | |
def inverse_sine_transform(F, k, x, **hints): | |
r""" | |
Compute the unitary, ordinary-frequency inverse sine transform of `F`, | |
defined as | |
.. math:: f(x) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty F(k) \sin(2\pi x k) \mathrm{d} k. | |
Explanation | |
=========== | |
If the transform cannot be computed in closed form, this | |
function returns an unevaluated :class:`InverseSineTransform` object. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Note that for this transform, by default ``noconds=True``. | |
Examples | |
======== | |
>>> from sympy import inverse_sine_transform, exp, sqrt, gamma | |
>>> from sympy.abc import x, k, a | |
>>> inverse_sine_transform(2**((1-2*a)/2)*k**(a - 1)* | |
... gamma(-a/2 + 1)/gamma((a+1)/2), k, x) | |
x**(-a) | |
>>> inverse_sine_transform(sqrt(2)*k*exp(-k**2/(4*a))/(4*sqrt(a)**3), k, x) | |
x*exp(-a*x**2) | |
See Also | |
======== | |
fourier_transform, inverse_fourier_transform | |
sine_transform | |
cosine_transform, inverse_cosine_transform | |
hankel_transform, inverse_hankel_transform | |
mellin_transform, laplace_transform | |
""" | |
return InverseSineTransform(F, k, x).doit(**hints) | |
class CosineTransform(SineCosineTypeTransform): | |
""" | |
Class representing unevaluated cosine transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute cosine transforms, see the :func:`cosine_transform` | |
docstring. | |
""" | |
_name = 'Cosine' | |
_kern = cos | |
def a(self): | |
return sqrt(2)/sqrt(pi) | |
def b(self): | |
return S.One | |
def cosine_transform(f, x, k, **hints): | |
r""" | |
Compute the unitary, ordinary-frequency cosine transform of `f`, defined | |
as | |
.. math:: F(k) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty f(x) \cos(2\pi x k) \mathrm{d} x. | |
Explanation | |
=========== | |
If the transform cannot be computed in closed form, this | |
function returns an unevaluated :class:`CosineTransform` object. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Note that for this transform, by default ``noconds=True``. | |
Examples | |
======== | |
>>> from sympy import cosine_transform, exp, sqrt, cos | |
>>> from sympy.abc import x, k, a | |
>>> cosine_transform(exp(-a*x), x, k) | |
sqrt(2)*a/(sqrt(pi)*(a**2 + k**2)) | |
>>> cosine_transform(exp(-a*sqrt(x))*cos(a*sqrt(x)), x, k) | |
a*exp(-a**2/(2*k))/(2*k**(3/2)) | |
See Also | |
======== | |
fourier_transform, inverse_fourier_transform, | |
sine_transform, inverse_sine_transform | |
inverse_cosine_transform | |
hankel_transform, inverse_hankel_transform | |
mellin_transform, laplace_transform | |
""" | |
return CosineTransform(f, x, k).doit(**hints) | |
class InverseCosineTransform(SineCosineTypeTransform): | |
""" | |
Class representing unevaluated inverse cosine transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute inverse cosine transforms, see the | |
:func:`inverse_cosine_transform` docstring. | |
""" | |
_name = 'Inverse Cosine' | |
_kern = cos | |
def a(self): | |
return sqrt(2)/sqrt(pi) | |
def b(self): | |
return S.One | |
def inverse_cosine_transform(F, k, x, **hints): | |
r""" | |
Compute the unitary, ordinary-frequency inverse cosine transform of `F`, | |
defined as | |
.. math:: f(x) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty F(k) \cos(2\pi x k) \mathrm{d} k. | |
Explanation | |
=========== | |
If the transform cannot be computed in closed form, this | |
function returns an unevaluated :class:`InverseCosineTransform` object. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Note that for this transform, by default ``noconds=True``. | |
Examples | |
======== | |
>>> from sympy import inverse_cosine_transform, sqrt, pi | |
>>> from sympy.abc import x, k, a | |
>>> inverse_cosine_transform(sqrt(2)*a/(sqrt(pi)*(a**2 + k**2)), k, x) | |
exp(-a*x) | |
>>> inverse_cosine_transform(1/sqrt(k), k, x) | |
1/sqrt(x) | |
See Also | |
======== | |
fourier_transform, inverse_fourier_transform, | |
sine_transform, inverse_sine_transform | |
cosine_transform | |
hankel_transform, inverse_hankel_transform | |
mellin_transform, laplace_transform | |
""" | |
return InverseCosineTransform(F, k, x).doit(**hints) | |
########################################################################## | |
# Hankel Transform | |
########################################################################## | |
def _hankel_transform(f, r, k, nu, name, simplify=True): | |
r""" | |
Compute a general Hankel transform | |
.. math:: F_\nu(k) = \int_{0}^\infty f(r) J_\nu(k r) r \mathrm{d} r. | |
""" | |
F = integrate(f*besselj(nu, k*r)*r, (r, S.Zero, S.Infinity)) | |
if not F.has(Integral): | |
return _simplify(F, simplify), S.true | |
if not F.is_Piecewise: | |
raise IntegralTransformError(name, f, 'could not compute integral') | |
F, cond = F.args[0] | |
if F.has(Integral): | |
raise IntegralTransformError(name, f, 'integral in unexpected form') | |
return _simplify(F, simplify), cond | |
class HankelTypeTransform(IntegralTransform): | |
""" | |
Base class for Hankel transforms. | |
""" | |
def doit(self, **hints): | |
return self._compute_transform(self.function, | |
self.function_variable, | |
self.transform_variable, | |
self.args[3], | |
**hints) | |
def _compute_transform(self, f, r, k, nu, **hints): | |
return _hankel_transform(f, r, k, nu, self._name, **hints) | |
def _as_integral(self, f, r, k, nu): | |
return Integral(f*besselj(nu, k*r)*r, (r, S.Zero, S.Infinity)) | |
def as_integral(self): | |
return self._as_integral(self.function, | |
self.function_variable, | |
self.transform_variable, | |
self.args[3]) | |
class HankelTransform(HankelTypeTransform): | |
""" | |
Class representing unevaluated Hankel transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute Hankel transforms, see the :func:`hankel_transform` | |
docstring. | |
""" | |
_name = 'Hankel' | |
def hankel_transform(f, r, k, nu, **hints): | |
r""" | |
Compute the Hankel transform of `f`, defined as | |
.. math:: F_\nu(k) = \int_{0}^\infty f(r) J_\nu(k r) r \mathrm{d} r. | |
Explanation | |
=========== | |
If the transform cannot be computed in closed form, this | |
function returns an unevaluated :class:`HankelTransform` object. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Note that for this transform, by default ``noconds=True``. | |
Examples | |
======== | |
>>> from sympy import hankel_transform, inverse_hankel_transform | |
>>> from sympy import exp | |
>>> from sympy.abc import r, k, m, nu, a | |
>>> ht = hankel_transform(1/r**m, r, k, nu) | |
>>> ht | |
2*k**(m - 2)*gamma(-m/2 + nu/2 + 1)/(2**m*gamma(m/2 + nu/2)) | |
>>> inverse_hankel_transform(ht, k, r, nu) | |
r**(-m) | |
>>> ht = hankel_transform(exp(-a*r), r, k, 0) | |
>>> ht | |
a/(k**3*(a**2/k**2 + 1)**(3/2)) | |
>>> inverse_hankel_transform(ht, k, r, 0) | |
exp(-a*r) | |
See Also | |
======== | |
fourier_transform, inverse_fourier_transform | |
sine_transform, inverse_sine_transform | |
cosine_transform, inverse_cosine_transform | |
inverse_hankel_transform | |
mellin_transform, laplace_transform | |
""" | |
return HankelTransform(f, r, k, nu).doit(**hints) | |
class InverseHankelTransform(HankelTypeTransform): | |
""" | |
Class representing unevaluated inverse Hankel transforms. | |
For usage of this class, see the :class:`IntegralTransform` docstring. | |
For how to compute inverse Hankel transforms, see the | |
:func:`inverse_hankel_transform` docstring. | |
""" | |
_name = 'Inverse Hankel' | |
def inverse_hankel_transform(F, k, r, nu, **hints): | |
r""" | |
Compute the inverse Hankel transform of `F` defined as | |
.. math:: f(r) = \int_{0}^\infty F_\nu(k) J_\nu(k r) k \mathrm{d} k. | |
Explanation | |
=========== | |
If the transform cannot be computed in closed form, this | |
function returns an unevaluated :class:`InverseHankelTransform` object. | |
For a description of possible hints, refer to the docstring of | |
:func:`sympy.integrals.transforms.IntegralTransform.doit`. | |
Note that for this transform, by default ``noconds=True``. | |
Examples | |
======== | |
>>> from sympy import hankel_transform, inverse_hankel_transform | |
>>> from sympy import exp | |
>>> from sympy.abc import r, k, m, nu, a | |
>>> ht = hankel_transform(1/r**m, r, k, nu) | |
>>> ht | |
2*k**(m - 2)*gamma(-m/2 + nu/2 + 1)/(2**m*gamma(m/2 + nu/2)) | |
>>> inverse_hankel_transform(ht, k, r, nu) | |
r**(-m) | |
>>> ht = hankel_transform(exp(-a*r), r, k, 0) | |
>>> ht | |
a/(k**3*(a**2/k**2 + 1)**(3/2)) | |
>>> inverse_hankel_transform(ht, k, r, 0) | |
exp(-a*r) | |
See Also | |
======== | |
fourier_transform, inverse_fourier_transform | |
sine_transform, inverse_sine_transform | |
cosine_transform, inverse_cosine_transform | |
hankel_transform | |
mellin_transform, laplace_transform | |
""" | |
return InverseHankelTransform(F, k, r, nu).doit(**hints) | |
########################################################################## | |
# Laplace Transform | |
########################################################################## | |
# Stub classes and functions that used to be here | |
import sympy.integrals.laplace as _laplace | |
LaplaceTransform = _laplace.LaplaceTransform | |
laplace_transform = _laplace.laplace_transform | |
laplace_correspondence = _laplace.laplace_correspondence | |
laplace_initial_conds = _laplace.laplace_initial_conds | |
InverseLaplaceTransform = _laplace.InverseLaplaceTransform | |
inverse_laplace_transform = _laplace.inverse_laplace_transform | |