12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789 |
- """ Integral Transforms """
- from functools import reduce, wraps
- from itertools import repeat
- from sympy.core import S, pi, I
- from sympy.core.add import Add
- from sympy.core.function import (AppliedUndef, count_ops, Derivative, expand,
- expand_complex, expand_mul, Function, Lambda,
- WildFunction)
- from sympy.core.mul import Mul
- from sympy.core.numbers import igcd, ilcm
- from sympy.core.relational import _canonical, Ge, Gt, Lt, Unequality, Eq
- from sympy.core.sorting import default_sort_key, ordered
- from sympy.core.symbol import Dummy, symbols, Wild
- from sympy.core.traversal import postorder_traversal
- from sympy.functions.combinatorial.factorials import factorial, rf
- from sympy.functions.elementary.complexes import (re, arg, Abs, polar_lift,
- periodic_argument)
- from sympy.functions.elementary.exponential import exp, log, exp_polar
- from sympy.functions.elementary.hyperbolic import cosh, coth, sinh, tanh, asinh
- from sympy.functions.elementary.integers import ceiling
- from sympy.functions.elementary.miscellaneous import Max, Min, sqrt
- from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold
- from sympy.functions.elementary.trigonometric import cos, cot, sin, tan, atan
- from sympy.functions.special.bessel import besseli, besselj, besselk, bessely
- from sympy.functions.special.delta_functions import DiracDelta, Heaviside
- from sympy.functions.special.error_functions import erf, erfc, Ei
- from sympy.functions.special.gamma_functions import digamma, gamma, lowergamma
- 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.matrices.matrices import MatrixBase
- from sympy.polys.matrices.linsolve import _lin_eq2dict, PolyNonlinearError
- from sympy.polys.polyroots import roots
- from sympy.polys.polytools import factor, Poly
- from sympy.polys.rationaltools import together
- from sympy.polys.rootoftools import CRootOf, RootSum
- from sympy.simplify import simplify, hyperexpand
- from sympy.simplify.powsimp import powdenest
- from sympy.solvers.inequalities import _solve_inequality
- from sympy.utilities.exceptions import (sympy_deprecation_warning,
- SymPyDeprecationWarning,
- ignore_warnings)
- 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.
- """
- @property
- def function(self):
- """ The function to be transformed. """
- return self.args[0]
- @property
- def function_variable(self):
- """ The dependent variable of the function to be transformed. """
- return self.args[1]
- @property
- def transform_variable(self):
- """ The independent transform variable. """
- return self.args[2]
- @property
- 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:
- 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 tuple([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:])))
- @property
- 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:
- 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):
- @wraps(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))
- @_noconds
- 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.
- """
- 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
- @_noconds_(True)
- 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:
- 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)
- @property
- 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)
- ##########################################################################
- # Laplace Transform
- ##########################################################################
- def _simplifyconds(expr, s, a):
- r"""
- Naively simplify some conditions occurring in ``expr``, given that `\operatorname{Re}(s) > a`.
- Examples
- ========
- >>> from sympy.integrals.transforms import _simplifyconds as simp
- >>> from sympy.abc import x
- >>> from sympy import sympify as S
- >>> simp(abs(x**2) < 1, x, 1)
- False
- >>> simp(abs(x**2) < 1, x, 2)
- False
- >>> simp(abs(x**2) < 1, x, 0)
- Abs(x**2) < 1
- >>> simp(abs(1/x**2) < 1, x, 1)
- True
- >>> simp(S(1) < abs(x), x, 1)
- True
- >>> simp(S(1) < abs(1/x), x, 1)
- False
- >>> from sympy import Ne
- >>> simp(Ne(1, x**3), x, 1)
- True
- >>> simp(Ne(1, x**3), x, 2)
- True
- >>> simp(Ne(1, x**3), x, 0)
- Ne(1, x**3)
- """
- def power(ex):
- if ex == s:
- return 1
- if ex.is_Pow and ex.base == s:
- return ex.exp
- return None
- def bigger(ex1, ex2):
- """ Return True only if |ex1| > |ex2|, False only if |ex1| < |ex2|.
- Else return None. """
- if ex1.has(s) and ex2.has(s):
- return None
- if isinstance(ex1, Abs):
- ex1 = ex1.args[0]
- if isinstance(ex2, Abs):
- ex2 = ex2.args[0]
- if ex1.has(s):
- return bigger(1/ex2, 1/ex1)
- n = power(ex2)
- if n is None:
- return None
- try:
- if n > 0 and (Abs(ex1) <= Abs(a)**n) == True:
- return False
- if n < 0 and (Abs(ex1) >= Abs(a)**n) == True:
- return True
- except TypeError:
- pass
- def replie(x, y):
- """ simplify x < y """
- if not (x.is_positive or isinstance(x, Abs)) \
- or not (y.is_positive or isinstance(y, Abs)):
- return (x < y)
- r = bigger(x, y)
- if r is not None:
- return not r
- return (x < y)
- def replue(x, y):
- b = bigger(x, y)
- if b in (True, False):
- return True
- return Unequality(x, y)
- def repl(ex, *args):
- if ex in (True, False):
- return bool(ex)
- return ex.replace(*args)
- from sympy.simplify.radsimp import collect_abs
- expr = collect_abs(expr)
- expr = repl(expr, Lt, replie)
- expr = repl(expr, Gt, lambda x, y: replie(y, x))
- expr = repl(expr, Unequality, replue)
- return S(expr)
- def expand_dirac_delta(expr):
- """
- Expand an expression involving DiractDelta to get it as a linear
- combination of DiracDelta functions.
- """
- return _lin_eq2dict(expr, expr.atoms(DiracDelta))
- @_noconds
- def _laplace_transform(f, t, s_, simplify=True):
- """ The backend function for Laplace transforms.
- This backend assumes that the frontend has already split sums
- such that `f` is to an addition anymore.
- """
- s = Dummy('s')
- a = Wild('a', exclude=[t])
- deltazero = []
- deltanonzero = []
- try:
- integratable, deltadict = expand_dirac_delta(f)
- except PolyNonlinearError:
- raise IntegralTransformError(
- 'Laplace', f, 'could not expand DiracDelta expressions')
- for dirac_func, dirac_coeff in deltadict.items():
- p = dirac_func.match(DiracDelta(a*t))
- if p:
- deltazero.append(dirac_coeff.subs(t,0)/p[a])
- else:
- if dirac_func.args[0].subs(t,0).is_zero:
- raise IntegralTransformError('Laplace', f,\
- 'not implemented yet.')
- else:
- deltanonzero.append(dirac_func*dirac_coeff)
- F = Add(integrate(exp(-s*t) * Add(integratable, *deltanonzero),
- (t, S.Zero, S.Infinity)),
- Add(*deltazero))
- if not F.has(Integral):
- return _simplify(F.subs(s, s_), simplify), S.NegativeInfinity, S.true
- if not F.is_Piecewise:
- raise IntegralTransformError(
- 'Laplace', f, 'could not compute integral')
- F, cond = F.args[0]
- if F.has(Integral):
- raise IntegralTransformError(
- 'Laplace', f, 'integral in unexpected form')
- def process_conds(conds):
- """ Turn ``conds`` into a strip and auxiliary conditions. """
- a = S.NegativeInfinity
- aux = S.true
- conds = conjuncts(to_cnf(conds))
- p, q, w1, w2, w3, w4, w5 = symbols(
- 'p q w1 w2 w3 w4 w5', cls=Wild, exclude=[s])
- patterns = (
- p*Abs(arg((s + w3)*q)) < w2,
- p*Abs(arg((s + w3)*q)) <= w2,
- Abs(periodic_argument((s + w3)**p*q, w1)) < w2,
- Abs(periodic_argument((s + w3)**p*q, w1)) <= w2,
- Abs(periodic_argument((polar_lift(s + w3))**p*q, w1)) < w2,
- Abs(periodic_argument((polar_lift(s + w3))**p*q, w1)) <= w2)
- for c in conds:
- a_ = S.Infinity
- aux_ = []
- for d in disjuncts(c):
- if d.is_Relational and s in d.rhs.free_symbols:
- d = d.reversed
- if d.is_Relational and isinstance(d, (Ge, Gt)):
- d = d.reversedsign
- for pat in patterns:
- m = d.match(pat)
- if m:
- break
- if m:
- if m[q].is_positive and m[w2]/m[p] == pi/2:
- d = -re(s + m[w3]) < 0
- m = d.match(p - cos(w1*Abs(arg(s*w5))*w2)*Abs(s**w3)**w4 < 0)
- if not m:
- m = d.match(
- cos(p - Abs(periodic_argument(s**w1*w5, q))*w2)*Abs(s**w3)**w4 < 0)
- if not m:
- m = d.match(
- p - cos(Abs(periodic_argument(polar_lift(s)**w1*w5, q))*w2
- )*Abs(s**w3)**w4 < 0)
- if m and all(m[wild].is_positive for wild in [w1, w2, w3, w4, w5]):
- d = re(s) > m[p]
- d_ = d.replace(
- re, lambda x: x.expand().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:
- raise IntegralTransformError('Laplace', f,
- 'convergence not in half-plane?')
- else:
- a_ = Min(soln.lts, a_)
- if a_ is not S.Infinity:
- a = Max(a_, a)
- else:
- aux = And(aux, Or(*aux_))
- return a, aux.canonical if aux.is_Relational else aux
- conds = [process_conds(c) for c in disjuncts(cond)]
- conds2 = [x for x in conds if x[1] != False and x[0] is not S.NegativeInfinity]
- if not conds2:
- conds2 = [x for x in conds if x[1] != False]
- conds = list(ordered(conds2))
- def cnt(expr):
- if expr in (True, False):
- return 0
- return expr.count_ops()
- conds.sort(key=lambda x: (-x[0], cnt(x[1])))
- if not conds:
- raise IntegralTransformError('Laplace', f, 'no convergence found')
- a, aux = conds[0] # XXX is [0] always the right one?
- def sbs(expr):
- return expr.subs(s, s_)
- if simplify:
- F = _simplifyconds(F, s, a)
- aux = _simplifyconds(aux, s, a)
- return _simplify(F.subs(s, s_), simplify), sbs(a), _canonical(sbs(aux))
- def _laplace_deep_collect(f, t):
- """
- This is an internal helper function that traverses through the epression
- tree of `f(t)` and collects arguments. The purpose of it is that
- anything like `f(w*t-1*t-c)` will be written as `f((w-1)*t-c)` such that
- it can match `f(a*t+b)`.
- """
- func = f.func
- args = list(f.args)
- if len(f.args) == 0:
- return f
- else:
- for k in range(len(args)):
- args[k] = _laplace_deep_collect(args[k], t)
- if func.is_Add:
- return func(*args).collect(t)
- else:
- return func(*args)
- def _laplace_build_rules(t, s):
- """
- This is an internal helper function that returns the table of Laplace
- transfrom rules in terms of the time variable `t` and the frequency
- variable `s`. It is used by `_laplace_apply_rules`.
- """
- a = Wild('a', exclude=[t])
- b = Wild('b', exclude=[t])
- n = Wild('n', exclude=[t])
- tau = Wild('tau', exclude=[t])
- omega = Wild('omega', exclude=[t])
- dco = lambda f: _laplace_deep_collect(f,t)
- laplace_transform_rules = [
- # ( time domain,
- # laplace domain,
- # condition, convergence plane, preparation function )
- #
- # Catch constant (would otherwise be treated by 2.12)
- (a, a/s, S.true, S.Zero, dco),
- # DiracDelta rules
- (DiracDelta(a*t-b),
- exp(-s*b/a)/Abs(a),
- Or(And(a>0, b>=0), And(a<0, b<=0)), S.Zero, dco),
- (DiracDelta(a*t-b),
- S(0),
- Or(And(a<0, b>=0), And(a>0, b<=0)), S.Zero, dco),
- # Rules from http://eqworld.ipmnet.ru/en/auxiliary/inttrans/
- # 2.1
- (1,
- 1/s,
- S.true, S.Zero, dco),
- # 2.2 expressed in terms of Heaviside
- (Heaviside(a*t-b),
- exp(-s*b/a)/s,
- And(a>0, b>0), S.Zero, dco),
- (Heaviside(a*t-b),
- (1-exp(-s*b/a))/s,
- And(a<0, b<0), S.Zero, dco),
- (Heaviside(a*t-b),
- 1/s,
- And(a>0, b<=0), S.Zero, dco),
- (Heaviside(a*t-b),
- 0,
- And(a<0, b>0), S.Zero, dco),
- # 2.3
- (t,
- 1/s**2,
- S.true, S.Zero, dco),
- # 2.4
- (1/(a*t+b),
- -exp(-b/a*s)*Ei(-b/a*s)/a,
- a>0, S.Zero, dco),
- # 2.5 and 2.6 are covered by 2.11
- # 2.7
- (1/sqrt(a*t+b),
- sqrt(a*pi/s)*exp(b/a*s)*erfc(sqrt(b/a*s))/a,
- a>0, S.Zero, dco),
- # 2.8
- (sqrt(t)/(t+b),
- sqrt(pi/s)-pi*sqrt(b)*exp(b*s)*erfc(sqrt(b*s)),
- S.true, S.Zero, dco),
- # 2.9
- ((a*t+b)**(-S(3)/2),
- 2*b**(-S(1)/2)-2*(pi*s/a)**(S(1)/2)*exp(b/a*s)*erfc(sqrt(b/a*s))/a,
- a>0, S.Zero, dco),
- # 2.10
- (t**(S(1)/2)*(t+a)**(-1),
- (pi/s)**(S(1)/2)-pi*a**(S(1)/2)*exp(a*s)*erfc(sqrt(a*s)),
- S.true, S.Zero, dco),
- # 2.11
- (1/(a*sqrt(t) + t**(3/2)),
- pi*a**(S(1)/2)*exp(a*s)*erfc(sqrt(a*s)),
- S.true, S.Zero, dco),
- # 2.12
- (t**n,
- gamma(n+1)/s**(n+1),
- n>-1, S.Zero, dco),
- # 2.13
- ((a*t+b)**n,
- lowergamma(n+1, b/a*s)*exp(-b/a*s)/s**(n+1)/a,
- And(n>-1, a>0), S.Zero, dco),
- # 2.14
- (t**n/(t+a),
- a**n*gamma(n+1)*lowergamma(-n,a*s),
- n>-1, S.Zero, dco),
- # 3.1
- (exp(a*t-tau),
- exp(-tau)/(s-a),
- S.true, a, dco),
- # 3.2
- (t*exp(a*t-tau),
- exp(-tau)/(s-a)**2,
- S.true, a, dco),
- # 3.3
- (t**n*exp(a*t),
- gamma(n+1)/(s-a)**(n+1),
- n>-1, a, dco),
- # 3.4 and 3.5 cannot be covered here because they are
- # sums and only the individual sum terms will get here.
- # 3.6
- (exp(-a*t**2),
- sqrt(pi/4/a)*exp(s**2/4/a)*erfc(s/sqrt(4*a)),
- a>0, S.Zero, dco),
- # 3.7
- (t*exp(-a*t**2),
- 1/(2*a)-2/sqrt(pi)/(4*a)**(S(3)/2)*s*erfc(s/sqrt(4*a)),
- S.true, S.Zero, dco),
- # 3.8
- (exp(-a/t),
- 2*sqrt(a/s)*besselk(1, 2*sqrt(a*s)),
- a>=0, S.Zero, dco),
- # 3.9
- (sqrt(t)*exp(-a/t),
- S(1)/2*sqrt(pi/s**3)*(1+2*sqrt(a*s))*exp(-2*sqrt(a*s)),
- a>=0, S.Zero, dco),
- # 3.10
- (exp(-a/t)/sqrt(t),
- sqrt(pi/s)*exp(-2*sqrt(a*s)),
- a>=0, S.Zero, dco),
- # 3.11
- (exp(-a/t)/(t*sqrt(t)),
- sqrt(pi/a)*exp(-2*sqrt(a*s)),
- a>0, S.Zero, dco),
- # 3.12
- (t**n*exp(-a/t),
- 2*(a/s)**((n+1)/2)*besselk(n+1, 2*sqrt(a*s)),
- a>0, S.Zero, dco),
- # 3.13
- (exp(-2*sqrt(a*t)),
- s**(-1)-sqrt(pi*a)*s**(-S(3)/2)*exp(a/s)*erfc(sqrt(a/s)),
- S.true, S.Zero, dco),
- # 3.14
- (exp(-2*sqrt(a*t))/sqrt(t),
- (pi/s)**(S(1)/2)*exp(a/s)*erfc(sqrt(a/s)),
- S.true, S.Zero, dco),
- # 4.1
- (sinh(a*t),
- a/(s**2-a**2),
- S.true, Abs(a), dco),
- # 4.2
- (sinh(a*t)**2,
- 2*a**2/(s**3-4*a**2*s**2),
- S.true, Abs(2*a), dco),
- # 4.3
- (sinh(a*t)/t,
- log((s+a)/(s-a))/2,
- S.true, a, dco),
- # 4.4
- (t**n*sinh(a*t),
- gamma(n+1)/2*((s-a)**(-n-1)-(s+a)**(-n-1)),
- n>-2, Abs(a), dco),
- # 4.5
- (sinh(2*sqrt(a*t)),
- sqrt(pi*a)/s/sqrt(s)*exp(a/s),
- S.true, S.Zero, dco),
- # 4.6
- (sqrt(t)*sinh(2*sqrt(a*t)),
- pi**(S(1)/2)*s**(-S(5)/2)*(s/2+a)*exp(a/s)*erf(sqrt(a/s))-a**(S(1)/2)*s**(-2),
- S.true, S.Zero, dco),
- # 4.7
- (sinh(2*sqrt(a*t))/sqrt(t),
- pi**(S(1)/2)*s**(-S(1)/2)*exp(a/s)*erf(sqrt(a/s)),
- S.true, S.Zero, dco),
- # 4.8
- (sinh(sqrt(a*t))**2/sqrt(t),
- pi**(S(1)/2)/2*s**(-S(1)/2)*(exp(a/s)-1),
- S.true, S.Zero, dco),
- # 4.9
- (cosh(a*t),
- s/(s**2-a**2),
- S.true, Abs(a), dco),
- # 4.10
- (cosh(a*t)**2,
- (s**2-2*a**2)/(s**3-4*a**2*s**2),
- S.true, Abs(2*a), dco),
- # 4.11
- (t**n*cosh(a*t),
- gamma(n+1)/2*((s-a)**(-n-1)+(s+a)**(-n-1)),
- n>-1, Abs(a), dco),
- # 4.12
- (cosh(2*sqrt(a*t)),
- 1/s+sqrt(pi*a)/s/sqrt(s)*exp(a/s)*erf(sqrt(a/s)),
- S.true, S.Zero, dco),
- # 4.13
- (sqrt(t)*cosh(2*sqrt(a*t)),
- pi**(S(1)/2)*s**(-S(5)/2)*(s/2+a)*exp(a/s),
- S.true, S.Zero, dco),
- # 4.14
- (cosh(2*sqrt(a*t))/sqrt(t),
- pi**(S(1)/2)*s**(-S(1)/2)*exp(a/s),
- S.true, S.Zero, dco),
- # 4.15
- (cosh(sqrt(a*t))**2/sqrt(t),
- pi**(S(1)/2)/2*s**(-S(1)/2)*(exp(a/s)+1),
- S.true, S.Zero, dco),
- # 5.1
- (log(a*t),
- -log(s/a+S.EulerGamma)/s,
- a>0, S.Zero, dco),
- # 5.2
- (log(1+a*t),
- -exp(s/a)/s*Ei(-s/a),
- S.true, S.Zero, dco),
- # 5.3
- (log(a*t+b),
- (log(b)-exp(s/b/a)/s*a*Ei(-s/b))/s*a,
- a>0, S.Zero, dco),
- # 5.4 is covered by 5.7
- # 5.5
- (log(t)/sqrt(t),
- -sqrt(pi/s)*(log(4*s)+S.EulerGamma),
- S.true, S.Zero, dco),
- # 5.6 is covered by 5.7
- # 5.7
- (t**n*log(t),
- gamma(n+1)*s**(-n-1)*(digamma(n+1)-log(s)),
- n>-1, S.Zero, dco),
- # 5.8
- (log(a*t)**2,
- ((log(s/a)+S.EulerGamma)**2+pi**2/6)/s,
- a>0, S.Zero, dco),
- # 5.9
- (exp(-a*t)*log(t),
- -(log(s+a)+S.EulerGamma)/(s+a),
- S.true, -a, dco),
- # 6.1
- (sin(omega*t),
- omega/(s**2+omega**2),
- S.true, S.Zero, dco),
- # 6.2
- (Abs(sin(omega*t)),
- omega/(s**2+omega**2)*coth(pi*s/2/omega),
- omega>0, S.Zero, dco),
- # 6.3 and 6.4 are covered by 1.8
- # 6.5 is covered by 1.8 together with 2.5
- # 6.6
- (sin(omega*t)/t,
- atan(omega/s),
- S.true, S.Zero, dco),
- # 6.7
- (sin(omega*t)**2/t,
- log(1+4*omega**2/s**2)/4,
- S.true, S.Zero, dco),
- # 6.8
- (sin(omega*t)**2/t**2,
- omega*atan(2*omega/s)-s*log(1+4*omega**2/s**2)/4,
- S.true, S.Zero, dco),
- # 6.9
- (sin(2*sqrt(a*t)),
- sqrt(pi*a)/s/sqrt(s)*exp(-a/s),
- a>0, S.Zero, dco),
- # 6.10
- (sin(2*sqrt(a*t))/t,
- pi*erf(sqrt(a/s)),
- a>0, S.Zero, dco),
- # 6.11
- (cos(omega*t),
- s/(s**2+omega**2),
- S.true, S.Zero, dco),
- # 6.12
- (cos(omega*t)**2,
- (s**2+2*omega**2)/(s**2+4*omega**2)/s,
- S.true, S.Zero, dco),
- # 6.13 is covered by 1.9 together with 2.5
- # 6.14 and 6.15 cannot be done with this method, the respective sum
- # parts do not converge. Solve elsewhere if really needed.
- # 6.16
- (sqrt(t)*cos(2*sqrt(a*t)),
- sqrt(pi)/2*s**(-S(5)/2)*(s-2*a)*exp(-a/s),
- a>0, S.Zero, dco),
- # 6.17
- (cos(2*sqrt(a*t))/sqrt(t),
- sqrt(pi/s)*exp(-a/s),
- a>0, S.Zero, dco),
- # 6.18
- (sin(a*t)*sin(b*t),
- 2*a*b*s/(s**2+(a+b)**2)/(s**2+(a-b)**2),
- S.true, S.Zero, dco),
- # 6.19
- (cos(a*t)*sin(b*t),
- b*(s**2-a**2+b**2)/(s**2+(a+b)**2)/(s**2+(a-b)**2),
- S.true, S.Zero, dco),
- # 6.20
- (cos(a*t)*cos(b*t),
- s*(s**2+a**2+b**2)/(s**2+(a+b)**2)/(s**2+(a-b)**2),
- S.true, S.Zero, dco),
- # 6.21
- (exp(b*t)*sin(a*t),
- a/((s-b)**2+a**2),
- S.true, b, dco),
- # 6.22
- (exp(b*t)*cos(a*t),
- (s-b)/((s-b)**2+a**2),
- S.true, b, dco),
- # 7.1
- (erf(a*t),
- exp(s**2/(2*a)**2)*erfc(s/(2*a))/s,
- a>0, S.Zero, dco),
- # 7.2
- (erf(sqrt(a*t)),
- sqrt(a)/sqrt(s+a)/s,
- a>0, S.Zero, dco),
- # 7.3
- (exp(a*t)*erf(sqrt(a*t)),
- sqrt(a)/sqrt(s)/(s-a),
- a>0, a, dco),
- # 7.4
- (erf(sqrt(a/t)/2),
- (1-exp(-sqrt(a*s)))/s,
- a>0, S.Zero, dco),
- # 7.5
- (erfc(sqrt(a*t)),
- (sqrt(s+a)-sqrt(a))/sqrt(s+a)/s,
- a>0, S.Zero, dco),
- # 7.6
- (exp(a*t)*erfc(sqrt(a*t)),
- 1/(s+sqrt(a*s)),
- a>0, S.Zero, dco),
- # 7.7
- (erfc(sqrt(a/t)/2),
- exp(-sqrt(a*s))/s,
- a>0, S.Zero, dco),
- # 8.1, 8.2
- (besselj(n, a*t),
- a**n/(sqrt(s**2+a**2)*(s+sqrt(s**2+a**2))**n),
- And(a>0, n>-1), S.Zero, dco),
- # 8.3, 8.4
- (t**b*besselj(n, a*t),
- 2**n/sqrt(pi)*gamma(n+S.Half)*a**n*(s**2+a**2)**(-n-S.Half),
- And(And(a>0, n>-S.Half), Eq(b, n)), S.Zero, dco),
- # 8.5
- (t**b*besselj(n, a*t),
- 2**(n+1)/sqrt(pi)*gamma(n+S(3)/2)*a**n*s*(s**2+a**2)**(-n-S(3)/2),
- And(And(a>0, n>-1), Eq(b, n+1)), S.Zero, dco),
- # 8.6
- (besselj(0, 2*sqrt(a*t)),
- exp(-a/s)/s,
- a>0, S.Zero, dco),
- # 8.7, 8.8
- (t**(b)*besselj(n, 2*sqrt(a*t)),
- a**(n/2)*s**(-n-1)*exp(-a/s),
- And(And(a>0, n>-1), Eq(b, n*S.Half)), S.Zero, dco),
- # 8.9
- (besselj(0, a*sqrt(t**2+b*t)),
- exp(b*s-b*sqrt(s**2+a**2))/sqrt(s**2+a**2),
- b>0, S.Zero, dco),
- # 8.10, 8.11
- (besseli(n, a*t),
- a**n/(sqrt(s**2-a**2)*(s+sqrt(s**2-a**2))**n),
- And(a>0, n>-1), Abs(a), dco),
- # 8.12
- (t**b*besseli(n, a*t),
- 2**n/sqrt(pi)*gamma(n+S.Half)*a**n*(s**2-a**2)**(-n-S.Half),
- And(And(a>0, n>-S.Half), Eq(b, n)), Abs(a), dco),
- # 8.13
- (t**b*besseli(n, a*t),
- 2**(n+1)/sqrt(pi)*gamma(n+S(3)/2)*a**n*s*(s**2-a**2)**(-n-S(3)/2),
- And(And(a>0, n>-1), Eq(b, n+1)), Abs(a), dco),
- # 8.15, 8.16
- (t**(b)*besseli(n, 2*sqrt(a*t)),
- a**(n/2)*s**(-n-1)*exp(a/s),
- And(And(a>0, n>-1), Eq(b, n*S.Half)), S.Zero, dco),
- # 8.17
- (bessely(0, a*t),
- -2/pi*asinh(s/a)/sqrt(s**2+a**2),
- a>0, S.Zero, dco),
- # 8.18
- (besselk(0, a*t),
- (log(s+sqrt(s**2-a**2)))/(sqrt(s**2-a**2)),
- a>0, Abs(a), dco)
- ]
- return laplace_transform_rules
- def _laplace_cr(f, a, c, **hints):
- """
- Internal helper function that will return `(f, a, c)` unless `**hints`
- contains `noconds=True`, in which case it will only return `f`.
- """
- conds = not hints.get('noconds', False)
- if conds:
- return f, a, c
- else:
- return f
- def _laplace_rule_timescale(f, t, s, doit=True, **hints):
- r"""
- This internal helper function tries to apply the time-scaling rule of the
- Laplace transform and returns `None` if it cannot do it.
- Time-scaling means the following: if $F(s)$ is the Laplace transform of,
- $f(t)$, then, for any $a>0$, the Laplace transform of $f(at)$ will be
- $\frac1a F(\frac{s}{a})$. This scaling will also affect the transform's
- convergence plane.
- """
- _simplify = hints.pop('simplify', True)
- b = Wild('b', exclude=[t])
- g = WildFunction('g', nargs=1)
- k, func = f.as_independent(t, as_Add=False)
- ma1 = func.match(g)
- if ma1:
- arg = ma1[g].args[0].collect(t)
- ma2 = arg.match(b*t)
- if ma2 and ma2[b]>0:
- debug('_laplace_apply_rules match:')
- debug(' f: %s ( %s, %s )'%(f, ma1, ma2))
- debug(' rule: amplitude and time scaling (1.1, 1.2)')
- if ma2[b]==1:
- if doit==True and not any(func.has(t) for func
- in ma1[g].atoms(AppliedUndef)):
- return k*_laplace_transform(ma1[g].func(t), t, s,
- simplify=_simplify)
- else:
- return k*LaplaceTransform(ma1[g].func(t), t, s, **hints)
- else:
- L = _laplace_apply_rules(ma1[g].func(t), t, s/ma2[b],
- doit=doit, **hints)
- try:
- r, p, c = L
- return (k/ma2[b]*r, p, c)
- except TypeError:
- return k/ma2[b]*L
- return None
- def _laplace_rule_heaviside(f, t, s, doit=True, **hints):
- """
- This internal helper function tries to transform a product containing the
- `Heaviside` function and returns `None` if it cannot do it.
- """
- hints.pop('simplify', True)
- a = Wild('a', exclude=[t])
- b = Wild('b', exclude=[t])
- y = Wild('y')
- g = WildFunction('g', nargs=1)
- k, func = f.as_independent(t, as_Add=False)
- ma1 = func.match(Heaviside(y)*g)
- if ma1:
- ma2 = ma1[y].match(t-a)
- ma3 = ma1[g].args[0].collect(t).match(t-b)
- if ma2 and ma2[a]>0 and ma3 and ma2[a]==ma3[b]:
- debug('_laplace_apply_rules match:')
- debug(' f: %s ( %s, %s, %s )'%(f, ma1, ma2, ma3))
- debug(' rule: time shift (1.3)')
- L = _laplace_apply_rules(ma1[g].func(t), t, s, doit=doit, **hints)
- try:
- r, p, c = L
- return (k*exp(-ma2[a]*s)*r, p, c)
- except TypeError:
- return k*exp(-ma2[a]*s)*L
- return None
- def _laplace_rule_exp(f, t, s, doit=True, **hints):
- """
- This internal helper function tries to transform a product containing the
- `exp` function and returns `None` if it cannot do it.
- """
- hints.pop('simplify', True)
- a = Wild('a', exclude=[t])
- y = Wild('y')
- z = Wild('z')
- k, func = f.as_independent(t, as_Add=False)
- ma1 = func.match(exp(y)*z)
- if ma1:
- ma2 = ma1[y].collect(t).match(a*t)
- if ma2:
- debug('_laplace_apply_rules match:')
- debug(' f: %s ( %s, %s )'%(f, ma1, ma2))
- debug(' rule: multiply with exp (1.5)')
- L = _laplace_apply_rules(ma1[z], t, s-ma2[a], doit=doit, **hints)
- try:
- r, p, c = L
- return (r, p+ma2[a], c)
- except TypeError:
- return L
- return None
- def _laplace_rule_trig(f, t, s, doit=True, **hints):
- """
- This internal helper function tries to transform a product containing a
- trigonometric function (`sin`, `cos`, `sinh`, `cosh`, ) and returns
- `None` if it cannot do it.
- """
- _simplify = hints.pop('simplify', True)
- a = Wild('a', exclude=[t])
- y = Wild('y')
- z = Wild('z')
- k, func = f.as_independent(t, as_Add=False)
- # All of the rules have a very similar form: trig(y)*z is matched, and then
- # two copies of the Laplace transform of z are shifted in the s Domain
- # and added with a weight; see rules 1.6 to 1.9 in
- # http://eqworld.ipmnet.ru/en/auxiliary/inttrans/laplace1.pdf
- # The parameters in the tuples are (fm, nu, s1, s2, sd):
- # fm: Function to match
- # nu: Number of the rule, for debug purposes
- # s1: weight of the sum, 'I' for sin and '1' for all others
- # s2: sign of the second copy of the Laplace transform of z
- # sd: shift direction; shift along real or imaginary axis if `1` or `I`
- trigrules = [(sinh(y), '1.6', 1, -1, 1), (cosh(y), '1.7', 1, 1, 1),
- (sin(y), '1.8', -I, -1, I), (cos(y), '1.9', 1, 1, I)]
- for trigrule in trigrules:
- fm, nu, s1, s2, sd = trigrule
- ma1 = func.match(fm*z)
- if ma1:
- ma2 = ma1[y].collect(t).match(a*t)
- if ma2:
- debug('_laplace_apply_rules match:')
- debug(' f: %s ( %s, %s )'%(f, ma1, ma2))
- debug(' rule: multiply with %s (%s)'%(fm.func, nu))
- L = _laplace_apply_rules(ma1[z], t, s, doit=doit, **hints)
- try:
- r, p, c = L
- # The convergence plane changes only if the shift has been
- # done along the real axis:
- if sd==1:
- cp_shift = Abs(ma2[a])
- else:
- cp_shift = 0
- return ((s1*(r.subs(s, s-sd*ma2[a])+\
- s2*r.subs(s, s+sd*ma2[a]))).simplify()/2,
- p+cp_shift, c)
- except TypeError:
- if doit==True and _simplify==True:
- return (s1*(L.subs(s, s-sd*ma2[a])+\
- s2*L.subs(s, s+sd*ma2[a]))).simplify()/2
- else:
- return (s1*(L.subs(s, s-sd*ma2[a])+\
- s2*L.subs(s, s+sd*ma2[a])))/2
- return None
- def _laplace_rule_diff(f, t, s, doit=True, **hints):
- """
- This internal helper function tries to transform an expression containing
- a derivative of an undefined function and returns `None` if it cannot
- do it.
- """
- hints.pop('simplify', True)
- a = Wild('a', exclude=[t])
- y = Wild('y')
- n = Wild('n', exclude=[t])
- g = WildFunction('g', nargs=1)
- ma1 = f.match(a*Derivative(g, (t, n)))
- if ma1 and ma1[g].args[0] == t and ma1[n].is_integer:
- debug('_laplace_apply_rules match:')
- debug(' f: %s'%(f,))
- debug(' rule: time derivative (1.11, 1.12)')
- d = []
- for k in range(ma1[n]):
- if k==0:
- y = ma1[g].func(t).subs(t, 0)
- else:
- y = Derivative(ma1[g].func(t), (t, k)).subs(t, 0)
- d.append(s**(ma1[n]-k-1)*y)
- r = s**ma1[n]*_laplace_apply_rules(ma1[g].func(t), t, s, doit=doit,
- **hints)
- return r - Add(*d)
- return None
- def _laplace_apply_rules(f, t, s, doit=True, **hints):
- """
- Helper function for the class LaplaceTransform.
- This function does a Laplace transform based on rules and, after
- applying the rules, hands the rest over to `_laplace_transform`, which
- will attempt to integrate.
- If it is called with `doit=False`, then it will instead return
- `LaplaceTransform` objects.
- """
- k, func = f.as_independent(t, as_Add=False)
- simple_rules = _laplace_build_rules(t, s)
- for t_dom, s_dom, check, plane, prep in simple_rules:
- ma = prep(func).match(t_dom)
- if ma:
- debug('_laplace_apply_rules match:')
- debug(' f: %s'%(func,))
- debug(' rule: %s o---o %s'%(t_dom, s_dom))
- try:
- debug(' try %s'%(check,))
- c = check.xreplace(ma)
- debug(' check %s -> %s'%(check, c))
- if c==True:
- return _laplace_cr(k*s_dom.xreplace(ma),
- plane.xreplace(ma), S.true, **hints)
- except Exception:
- debug('_laplace_apply_rules did not match.')
- if f.has(DiracDelta):
- return None
- prog_rules = [_laplace_rule_timescale, _laplace_rule_heaviside,
- _laplace_rule_exp, _laplace_rule_trig, _laplace_rule_diff]
- for p_rule in prog_rules:
- LT = p_rule(f, t, s, doit=doit, **hints)
- if LT is not None:
- return LT
- return None
- class LaplaceTransform(IntegralTransform):
- """
- Class representing unevaluated Laplace transforms.
- For usage of this class, see the :class:`IntegralTransform` docstring.
- For how to compute Laplace transforms, see the :func:`laplace_transform`
- docstring.
- """
- _name = 'Laplace'
- def _compute_transform(self, f, t, s, **hints):
- LT = _laplace_apply_rules(f, t, s, **hints)
- if LT is None:
- _simplify = hints.pop('simplify', True)
- debug('_laplace_apply_rules could not match function %s'%(f,))
- debug(' hints: %s'%(hints,))
- return _laplace_transform(f, t, s, simplify=_simplify, **hints)
- else:
- return LT
- def _as_integral(self, f, t, s):
- return Integral(f*exp(-s*t), (t, S.Zero, S.Infinity))
- def _collapse_extra(self, extra):
- conds = []
- planes = []
- for plane, cond in extra:
- conds.append(cond)
- planes.append(plane)
- cond = And(*conds)
- plane = Max(*planes)
- if cond == False:
- raise IntegralTransformError(
- 'Laplace', None, 'No combined convergence.')
- return plane, cond
- def _try_directly(self, **hints):
- fn = self.function
- debug('----> _try_directly: %s'%(fn, ))
- t_ = self.function_variable
- s_ = self.transform_variable
- LT = None
- if not fn.is_Add:
- fn = expand_mul(fn)
- try:
- LT = self._compute_transform(fn, t_, s_, **hints)
- except IntegralTransformError:
- LT = None
- return fn, LT
- def laplace_transform(f, t, s, legacy_matrix=True, **hints):
- r"""
- Compute the Laplace Transform `F(s)` of `f(t)`,
- .. math :: F(s) = \int_{0^{-}}^\infty e^{-st} f(t) \mathrm{d}t.
- Explanation
- ===========
- For all sensible functions, this converges absolutely in a
- half-plane
- .. math :: a < \operatorname{Re}(s)
- This function returns ``(F, a, cond)`` where ``F`` is the Laplace
- transform of ``f``, `a` is the half-plane of convergence, and `cond` are
- auxiliary convergence conditions.
- The implementation is rule-based, and if you are interested in which
- rules are applied, and whether integration is attemped, you can switch
- debug information on by setting ``sympy.SYMPY_DEBUG=True``.
- The lower bound is `0-`, meaning that this bound should be approached
- from the lower side. This is only necessary if distributions are involved.
- At present, it is only done if `f(t)` contains ``DiracDelta``, in which
- case the Laplace transform is computed implicitly as
- .. math :: F(s) = \lim_{\tau\to 0^{-}} \int_{\tau}^\infty e^{-st} f(t) \mathrm{d}t
- by applying rules.
- If the integral cannot be fully computed in closed form, this function
- returns an unevaluated :class:`LaplaceTransform` object.
- For a description of possible hints, refer to the docstring of
- :func:`sympy.integrals.transforms.IntegralTransform.doit`. If ``noconds=True``,
- only `F` will be returned (i.e. not ``cond``, and also not the plane ``a``).
- .. deprecated:: 1.9
- Legacy behavior for matrices where ``laplace_transform`` with
- ``noconds=False`` (the default) returns a Matrix whose elements are
- tuples. The behavior of ``laplace_transform`` for matrices will change
- in a future release of SymPy to return a tuple of the transformed
- Matrix and the convergence conditions for the matrix as a whole. Use
- ``legacy_matrix=False`` to enable the new behavior.
- Examples
- ========
- >>> from sympy import DiracDelta, exp, laplace_transform
- >>> from sympy.abc import t, s, a
- >>> laplace_transform(t**4, t, s)
- (24/s**5, 0, True)
- >>> laplace_transform(t**a, t, s)
- (gamma(a + 1)/(s*s**a), 0, re(a) > -1)
- >>> laplace_transform(DiracDelta(t)-a*exp(-a*t),t,s)
- (s/(a + s), Max(0, -a), True)
- See Also
- ========
- inverse_laplace_transform, mellin_transform, fourier_transform
- hankel_transform, inverse_hankel_transform
- """
- debug('\n***** laplace_transform(%s, %s, %s)'%(f, t, s))
- if isinstance(f, MatrixBase) and hasattr(f, 'applyfunc'):
- conds = not hints.get('noconds', False)
- if conds and legacy_matrix:
- sympy_deprecation_warning(
- """
- Calling laplace_transform() on a Matrix with noconds=False (the default) is
- deprecated. Either noconds=True or use legacy_matrix=False to get the new
- behavior.
- """,
- deprecated_since_version="1.9",
- active_deprecations_target="deprecated-laplace-transform-matrix",
- )
- # Temporarily disable the deprecation warning for non-Expr objects
- # in Matrix
- with ignore_warnings(SymPyDeprecationWarning):
- return f.applyfunc(lambda fij: laplace_transform(fij, t, s, **hints))
- else:
- elements_trans = [laplace_transform(fij, t, s, **hints) for fij in f]
- if conds:
- elements, avals, conditions = zip(*elements_trans)
- f_laplace = type(f)(*f.shape, elements)
- return f_laplace, Max(*avals), And(*conditions)
- else:
- return type(f)(*f.shape, elements_trans)
- return LaplaceTransform(f, t, s).doit(**hints)
- @_noconds_(True)
- def _inverse_laplace_transform(F, s, t_, plane, simplify=True):
- """ The backend function for inverse Laplace transforms. """
- from sympy.integrals.meijerint import meijerint_inversion, _get_coeff_exp
- # There are two strategies we can try:
- # 1) Use inverse mellin transforms - related by a simple change of variables.
- # 2) Use the inversion integral.
- t = Dummy('t', real=True)
- def pw_simp(*args):
- """ Simplify a piecewise expression from hyperexpand. """
- # XXX we break modularity here!
- if len(args) != 3:
- return Piecewise(*args)
- arg = args[2].args[0].argument
- coeff, exponent = _get_coeff_exp(arg, t)
- e1 = args[0].args[0]
- e2 = args[1].args[0]
- return Heaviside(1/Abs(coeff) - t**exponent)*e1 \
- + Heaviside(t**exponent - 1/Abs(coeff))*e2
- if F.is_rational_function(s):
- F = F.apart(s)
- if F.is_Add:
- f = Add(*[_inverse_laplace_transform(X, s, t, plane, simplify)\
- for X in F.args])
- return _simplify(f.subs(t, t_), simplify), True
- try:
- f, cond = inverse_mellin_transform(F, s, exp(-t), (None, S.Infinity),
- needeval=True, noconds=False)
- except IntegralTransformError:
- f = None
- if f is None:
- f = meijerint_inversion(F, s, t)
- if f is None:
- raise IntegralTransformError('Inverse Laplace', f, '')
- if f.is_Piecewise:
- f, cond = f.args[0]
- if f.has(Integral):
- raise IntegralTransformError('Inverse Laplace', f,
- 'inversion integral of unrecognised form.')
- else:
- cond = S.true
- f = f.replace(Piecewise, pw_simp)
- if f.is_Piecewise:
- # many of the functions called below can't work with piecewise
- # (b/c it has a bool in args)
- return f.subs(t, t_), cond
- u = Dummy('u')
- def simp_heaviside(arg, H0=S.Half):
- a = arg.subs(exp(-t), u)
- if a.has(t):
- return Heaviside(arg, H0)
- rel = _solve_inequality(a > 0, u)
- if rel.lts == u:
- k = log(rel.gts)
- return Heaviside(t + k, H0)
- else:
- k = log(rel.lts)
- return Heaviside(-(t + k), H0)
- f = f.replace(Heaviside, simp_heaviside)
- def simp_exp(arg):
- return expand_complex(exp(arg))
- f = f.replace(exp, simp_exp)
- # TODO it would be nice to fix cosh and sinh ... simplify messes these
- # exponentials up
- return _simplify(f.subs(t, t_), simplify), cond
- class InverseLaplaceTransform(IntegralTransform):
- """
- Class representing unevaluated inverse Laplace transforms.
- For usage of this class, see the :class:`IntegralTransform` docstring.
- For how to compute inverse Laplace transforms, see the
- :func:`inverse_laplace_transform` docstring.
- """
- _name = 'Inverse Laplace'
- _none_sentinel = Dummy('None')
- _c = Dummy('c')
- def __new__(cls, F, s, x, plane, **opts):
- if plane is None:
- plane = InverseLaplaceTransform._none_sentinel
- return IntegralTransform.__new__(cls, F, s, x, plane, **opts)
- @property
- def fundamental_plane(self):
- plane = self.args[3]
- if plane is InverseLaplaceTransform._none_sentinel:
- plane = None
- return plane
- def _compute_transform(self, F, s, t, **hints):
- return _inverse_laplace_transform(F, s, t, self.fundamental_plane, **hints)
- def _as_integral(self, F, s, t):
- c = self.__class__._c
- return Integral(exp(s*t)*F, (s, c - S.ImaginaryUnit*S.Infinity,
- c + S.ImaginaryUnit*S.Infinity))/(2*S.Pi*S.ImaginaryUnit)
- def inverse_laplace_transform(F, s, t, plane=None, **hints):
- r"""
- Compute the inverse Laplace transform of `F(s)`, defined as
- .. math :: f(t) = \frac{1}{2\pi i} \int_{c-i\infty}^{c+i\infty} e^{st} F(s) \mathrm{d}s,
- for `c` so large that `F(s)` has no singularites in the
- half-plane `\operatorname{Re}(s) > c-\epsilon`.
- Explanation
- ===========
- The plane can be specified by
- argument ``plane``, but will be inferred if passed as None.
- Under certain regularity conditions, this recovers `f(t)` from its
- Laplace Transform `F(s)`, for non-negative `t`, and vice
- versa.
- If the integral cannot be computed in closed form, this function returns
- an unevaluated :class:`InverseLaplaceTransform` object.
- Note that this function will always assume `t` to be real,
- regardless of the SymPy assumption on `t`.
- For a description of possible hints, refer to the docstring of
- :func:`sympy.integrals.transforms.IntegralTransform.doit`.
- Examples
- ========
- >>> from sympy import inverse_laplace_transform, exp, Symbol
- >>> from sympy.abc import s, t
- >>> a = Symbol('a', positive=True)
- >>> inverse_laplace_transform(exp(-a*s)/s, s, t)
- Heaviside(-a + t)
- See Also
- ========
- laplace_transform, _fast_inverse_laplace
- hankel_transform, inverse_hankel_transform
- """
- if isinstance(F, MatrixBase) and hasattr(F, 'applyfunc'):
- return F.applyfunc(lambda Fij: inverse_laplace_transform(Fij, s, t, plane, **hints))
- return InverseLaplaceTransform(F, s, t, plane).doit(**hints)
- def _fast_inverse_laplace(e, s, t):
- """Fast inverse Laplace transform of rational function including RootSum"""
- a, b, n = symbols('a, b, n', cls=Wild, exclude=[s])
- def _ilt(e):
- if not e.has(s):
- return e
- elif e.is_Add:
- return _ilt_add(e)
- elif e.is_Mul:
- return _ilt_mul(e)
- elif e.is_Pow:
- return _ilt_pow(e)
- elif isinstance(e, RootSum):
- return _ilt_rootsum(e)
- else:
- raise NotImplementedError
- def _ilt_add(e):
- return e.func(*map(_ilt, e.args))
- def _ilt_mul(e):
- coeff, expr = e.as_independent(s)
- if expr.is_Mul:
- raise NotImplementedError
- return coeff * _ilt(expr)
- def _ilt_pow(e):
- match = e.match((a*s + b)**n)
- if match is not None:
- nm, am, bm = match[n], match[a], match[b]
- if nm.is_Integer and nm < 0:
- return t**(-nm-1)*exp(-(bm/am)*t)/(am**-nm*gamma(-nm))
- if nm == 1:
- return exp(-(bm/am)*t) / am
- raise NotImplementedError
- def _ilt_rootsum(e):
- expr = e.fun.expr
- [variable] = e.fun.variables
- return RootSum(e.poly, Lambda(variable, together(_ilt(expr))))
- return _ilt(e)
- ##########################################################################
- # Fourier Transform
- ##########################################################################
- @_noconds_(True)
- 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
- ##########################################################################
- @_noconds_(True)
- 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
- ##########################################################################
- @_noconds_(True)
- 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))
- @property
- 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)
|