You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1762 lines
62 KiB
1762 lines
62 KiB
5 months ago
|
"""Laplace Transforms"""
|
||
|
from sympy.core import S, pi, I
|
||
|
from sympy.core.add import Add
|
||
|
from sympy.core.cache import cacheit
|
||
|
from sympy.core.function import (
|
||
|
AppliedUndef, Derivative, expand, expand_complex, expand_mul, expand_trig,
|
||
|
Lambda, WildFunction, diff)
|
||
|
from sympy.core.mul import Mul, prod
|
||
|
from sympy.core.relational import _canonical, Ge, Gt, Lt, Unequality, Eq
|
||
|
from sympy.core.sorting import ordered
|
||
|
from sympy.core.symbol import Dummy, symbols, Wild
|
||
|
from sympy.functions.elementary.complexes import (
|
||
|
re, im, arg, Abs, polar_lift, periodic_argument)
|
||
|
from sympy.functions.elementary.exponential import exp, log
|
||
|
from sympy.functions.elementary.hyperbolic import cosh, coth, sinh, asinh
|
||
|
from sympy.functions.elementary.miscellaneous import Max, Min, sqrt
|
||
|
from sympy.functions.elementary.piecewise import Piecewise
|
||
|
from sympy.functions.elementary.trigonometric import cos, sin, 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.integrals import integrate, Integral
|
||
|
from sympy.integrals.transforms import (
|
||
|
_simplify, IntegralTransform, IntegralTransformError)
|
||
|
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
|
||
|
from sympy.polys.polyerrors import PolynomialError
|
||
|
from sympy.polys.polyroots import roots
|
||
|
from sympy.polys.polytools import Poly
|
||
|
from sympy.polys.rationaltools import together
|
||
|
from sympy.polys.rootoftools import RootSum
|
||
|
from sympy.utilities.exceptions import (
|
||
|
sympy_deprecation_warning, SymPyDeprecationWarning, ignore_warnings)
|
||
|
from sympy.utilities.misc import debug, debugf
|
||
|
|
||
|
|
||
|
def _simplifyconds(expr, s, a):
|
||
|
r"""
|
||
|
Naively simplify some conditions occurring in ``expr``,
|
||
|
given that `\operatorname{Re}(s) > a`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.integrals.laplace import _simplifyconds
|
||
|
>>> from sympy.abc import x
|
||
|
>>> from sympy import sympify as S
|
||
|
>>> _simplifyconds(abs(x**2) < 1, x, 1)
|
||
|
False
|
||
|
>>> _simplifyconds(abs(x**2) < 1, x, 2)
|
||
|
False
|
||
|
>>> _simplifyconds(abs(x**2) < 1, x, 0)
|
||
|
Abs(x**2) < 1
|
||
|
>>> _simplifyconds(abs(1/x**2) < 1, x, 1)
|
||
|
True
|
||
|
>>> _simplifyconds(S(1) < abs(x), x, 1)
|
||
|
True
|
||
|
>>> _simplifyconds(S(1) < abs(1/x), x, 1)
|
||
|
False
|
||
|
|
||
|
>>> from sympy import Ne
|
||
|
>>> _simplifyconds(Ne(1, x**3), x, 1)
|
||
|
True
|
||
|
>>> _simplifyconds(Ne(1, x**3), x, 2)
|
||
|
True
|
||
|
>>> _simplifyconds(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) == S.true:
|
||
|
return False
|
||
|
if n < 0 and (Abs(ex1) >= Abs(a)**n) == S.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))
|
||
|
|
||
|
|
||
|
def _laplace_transform_integration(f, t, s_, simplify=True):
|
||
|
""" The backend function for doing Laplace transforms by integration.
|
||
|
|
||
|
This backend assumes that the frontend has already split sums
|
||
|
such that `f` is to an addition anymore.
|
||
|
"""
|
||
|
s = Dummy('s')
|
||
|
debugf('[LT _l_t_i ] started with (%s, %s, %s)', (f, t, s))
|
||
|
debugf('[LT _l_t_i ] and simplify=%s', (simplify, ))
|
||
|
|
||
|
if f.has(DiracDelta):
|
||
|
return None
|
||
|
|
||
|
F = integrate(f*exp(-s*t), (t, S.Zero, S.Infinity))
|
||
|
debugf('[LT _l_t_i ] integrated: %s', (F, ))
|
||
|
|
||
|
if not F.has(Integral):
|
||
|
return _simplify(F.subs(s, s_), simplify), S.NegativeInfinity, S.true
|
||
|
|
||
|
if not F.is_Piecewise:
|
||
|
debug('[LT _l_t_i ] not piecewise.')
|
||
|
return None
|
||
|
|
||
|
F, cond = F.args[0]
|
||
|
if F.has(Integral):
|
||
|
debug('[LT _l_t_i ] integral in unexpected form.')
|
||
|
return None
|
||
|
|
||
|
def process_conds(conds):
|
||
|
""" Turn ``conds`` into a strip and auxiliary conditions. """
|
||
|
from sympy.solvers.inequalities import _solve_inequality
|
||
|
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 and 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:
|
||
|
debug('[LT _l_t_i ] convergence not in half-plane.')
|
||
|
return None
|
||
|
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] !=
|
||
|
S.false and x[0] is not S.NegativeInfinity]
|
||
|
if not conds2:
|
||
|
conds2 = [x for x in conds if x[1] != S.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:
|
||
|
debug('[LT _l_t_i ] no convergence found.')
|
||
|
return None
|
||
|
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:
|
||
|
args = [_laplace_deep_collect(arg, t) for arg in args]
|
||
|
if func.is_Add:
|
||
|
return func(*args).collect(t)
|
||
|
else:
|
||
|
return func(*args)
|
||
|
|
||
|
|
||
|
@cacheit
|
||
|
def _laplace_build_rules():
|
||
|
"""
|
||
|
This is an internal helper function that returns the table of Laplace
|
||
|
transform rules in terms of the time variable `t` and the frequency
|
||
|
variable `s`. It is used by ``_laplace_apply_rules``. Each entry is a
|
||
|
tuple containing:
|
||
|
|
||
|
(time domain pattern,
|
||
|
frequency-domain replacement,
|
||
|
condition for the rule to be applied,
|
||
|
convergence plane,
|
||
|
preparation function)
|
||
|
|
||
|
The preparation function is a function with one argument that is applied
|
||
|
to the expression before matching. For most rules it should be
|
||
|
``_laplace_deep_collect``.
|
||
|
"""
|
||
|
t = Dummy('t')
|
||
|
s = Dummy('s')
|
||
|
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])
|
||
|
def dco(f): return _laplace_deep_collect(f, t)
|
||
|
debug('_laplace_build_rules is building rules')
|
||
|
|
||
|
laplace_transform_rules = [
|
||
|
(a, a/s,
|
||
|
S.true, S.Zero, dco), # 4.2.1
|
||
|
(DiracDelta(a*t-b), exp(-s*b/a)/Abs(a),
|
||
|
Or(And(a > 0, b >= 0), And(a < 0, b <= 0)),
|
||
|
S.NegativeInfinity, dco), # Not in Bateman54
|
||
|
(DiracDelta(a*t-b), S(0),
|
||
|
Or(And(a < 0, b >= 0), And(a > 0, b <= 0)),
|
||
|
S.NegativeInfinity, dco), # Not in Bateman54
|
||
|
(Heaviside(a*t-b), exp(-s*b/a)/s,
|
||
|
And(a > 0, b > 0), S.Zero, dco), # 4.4.1
|
||
|
(Heaviside(a*t-b), (1-exp(-s*b/a))/s,
|
||
|
And(a < 0, b < 0), S.Zero, dco), # 4.4.1
|
||
|
(Heaviside(a*t-b), 1/s,
|
||
|
And(a > 0, b <= 0), S.Zero, dco), # 4.4.1
|
||
|
(Heaviside(a*t-b), 0,
|
||
|
And(a < 0, b > 0), S.Zero, dco), # 4.4.1
|
||
|
(t, 1/s**2,
|
||
|
S.true, S.Zero, dco), # 4.2.3
|
||
|
(1/(a*t+b), -exp(-b/a*s)*Ei(-b/a*s)/a,
|
||
|
Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.6
|
||
|
(1/sqrt(a*t+b), sqrt(a*pi/s)*exp(b/a*s)*erfc(sqrt(b/a*s))/a,
|
||
|
Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.18
|
||
|
((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,
|
||
|
Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.20
|
||
|
(sqrt(t)/(t+b), sqrt(pi/s)-pi*sqrt(b)*exp(b*s)*erfc(sqrt(b*s)),
|
||
|
Abs(arg(b)) < pi, S.Zero, dco), # 4.2.22
|
||
|
(1/(a*sqrt(t) + t**(3/2)), pi*a**(S(1)/2)*exp(a*s)*erfc(sqrt(a*s)),
|
||
|
S.true, S.Zero, dco), # Not in Bateman54
|
||
|
(t**n, gamma(n+1)/s**(n+1),
|
||
|
n > -1, S.Zero, dco), # 4.3.1
|
||
|
((a*t+b)**n, lowergamma(n+1, b/a*s)*exp(-b/a*s)/s**(n+1)/a,
|
||
|
And(n > -1, Abs(arg(b/a)) < pi), S.Zero, dco), # 4.3.4
|
||
|
(t**n/(t+a), a**n*gamma(n+1)*lowergamma(-n, a*s),
|
||
|
And(n > -1, Abs(arg(a)) < pi), S.Zero, dco), # 4.3.7
|
||
|
(exp(a*t-tau), exp(-tau)/(s-a),
|
||
|
S.true, re(a), dco), # 4.5.1
|
||
|
(t*exp(a*t-tau), exp(-tau)/(s-a)**2,
|
||
|
S.true, re(a), dco), # 4.5.2
|
||
|
(t**n*exp(a*t), gamma(n+1)/(s-a)**(n+1),
|
||
|
re(n) > -1, re(a), dco), # 4.5.3
|
||
|
(exp(-a*t**2), sqrt(pi/4/a)*exp(s**2/4/a)*erfc(s/sqrt(4*a)),
|
||
|
re(a) > 0, S.Zero, dco), # 4.5.21
|
||
|
(t*exp(-a*t**2),
|
||
|
1/(2*a)-2/sqrt(pi)/(4*a)**(S(3)/2)*s*erfc(s/sqrt(4*a)),
|
||
|
re(a) > 0, S.Zero, dco), # 4.5.22
|
||
|
(exp(-a/t), 2*sqrt(a/s)*besselk(1, 2*sqrt(a*s)),
|
||
|
re(a) >= 0, S.Zero, dco), # 4.5.25
|
||
|
(sqrt(t)*exp(-a/t),
|
||
|
S(1)/2*sqrt(pi/s**3)*(1+2*sqrt(a*s))*exp(-2*sqrt(a*s)),
|
||
|
re(a) >= 0, S.Zero, dco), # 4.5.26
|
||
|
(exp(-a/t)/sqrt(t), sqrt(pi/s)*exp(-2*sqrt(a*s)),
|
||
|
re(a) >= 0, S.Zero, dco), # 4.5.27
|
||
|
(exp(-a/t)/(t*sqrt(t)), sqrt(pi/a)*exp(-2*sqrt(a*s)),
|
||
|
re(a) > 0, S.Zero, dco), # 4.5.28
|
||
|
(t**n*exp(-a/t), 2*(a/s)**((n+1)/2)*besselk(n+1, 2*sqrt(a*s)),
|
||
|
re(a) > 0, S.Zero, dco), # 4.5.29
|
||
|
(exp(-2*sqrt(a*t)),
|
||
|
s**(-1)-sqrt(pi*a)*s**(-S(3)/2)*exp(a/s) * erfc(sqrt(a/s)),
|
||
|
Abs(arg(a)) < pi, S.Zero, dco), # 4.5.31
|
||
|
(exp(-2*sqrt(a*t))/sqrt(t), (pi/s)**(S(1)/2)*exp(a/s)*erfc(sqrt(a/s)),
|
||
|
Abs(arg(a)) < pi, S.Zero, dco), # 4.5.33
|
||
|
(log(a*t), -log(exp(S.EulerGamma)*s/a)/s,
|
||
|
a > 0, S.Zero, dco), # 4.6.1
|
||
|
(log(1+a*t), -exp(s/a)/s*Ei(-s/a),
|
||
|
Abs(arg(a)) < pi, S.Zero, dco), # 4.6.4
|
||
|
(log(a*t+b), (log(b)-exp(s/b/a)/s*a*Ei(-s/b))/s*a,
|
||
|
And(a > 0, Abs(arg(b)) < pi), S.Zero, dco), # 4.6.5
|
||
|
(log(t)/sqrt(t), -sqrt(pi/s)*log(4*s*exp(S.EulerGamma)),
|
||
|
S.true, S.Zero, dco), # 4.6.9
|
||
|
(t**n*log(t), gamma(n+1)*s**(-n-1)*(digamma(n+1)-log(s)),
|
||
|
re(n) > -1, S.Zero, dco), # 4.6.11
|
||
|
(log(a*t)**2, (log(exp(S.EulerGamma)*s/a)**2+pi**2/6)/s,
|
||
|
a > 0, S.Zero, dco), # 4.6.13
|
||
|
(sin(omega*t), omega/(s**2+omega**2),
|
||
|
S.true, Abs(im(omega)), dco), # 4,7,1
|
||
|
(Abs(sin(omega*t)), omega/(s**2+omega**2)*coth(pi*s/2/omega),
|
||
|
omega > 0, S.Zero, dco), # 4.7.2
|
||
|
(sin(omega*t)/t, atan(omega/s),
|
||
|
S.true, Abs(im(omega)), dco), # 4.7.16
|
||
|
(sin(omega*t)**2/t, log(1+4*omega**2/s**2)/4,
|
||
|
S.true, 2*Abs(im(omega)), dco), # 4.7.17
|
||
|
(sin(omega*t)**2/t**2,
|
||
|
omega*atan(2*omega/s)-s*log(1+4*omega**2/s**2)/4,
|
||
|
S.true, 2*Abs(im(omega)), dco), # 4.7.20
|
||
|
(sin(2*sqrt(a*t)), sqrt(pi*a)/s/sqrt(s)*exp(-a/s),
|
||
|
S.true, S.Zero, dco), # 4.7.32
|
||
|
(sin(2*sqrt(a*t))/t, pi*erf(sqrt(a/s)),
|
||
|
S.true, S.Zero, dco), # 4.7.34
|
||
|
(cos(omega*t), s/(s**2+omega**2),
|
||
|
S.true, Abs(im(omega)), dco), # 4.7.43
|
||
|
(cos(omega*t)**2, (s**2+2*omega**2)/(s**2+4*omega**2)/s,
|
||
|
S.true, 2*Abs(im(omega)), dco), # 4.7.45
|
||
|
(sqrt(t)*cos(2*sqrt(a*t)), sqrt(pi)/2*s**(-S(5)/2)*(s-2*a)*exp(-a/s),
|
||
|
S.true, S.Zero, dco), # 4.7.66
|
||
|
(cos(2*sqrt(a*t))/sqrt(t), sqrt(pi/s)*exp(-a/s),
|
||
|
S.true, S.Zero, dco), # 4.7.67
|
||
|
(sin(a*t)*sin(b*t), 2*a*b*s/(s**2+(a+b)**2)/(s**2+(a-b)**2),
|
||
|
S.true, Abs(im(a))+Abs(im(b)), dco), # 4.7.78
|
||
|
(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, Abs(im(a))+Abs(im(b)), dco), # 4.7.79
|
||
|
(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, Abs(im(a))+Abs(im(b)), dco), # 4.7.80
|
||
|
(sinh(a*t), a/(s**2-a**2),
|
||
|
S.true, Abs(re(a)), dco), # 4.9.1
|
||
|
(cosh(a*t), s/(s**2-a**2),
|
||
|
S.true, Abs(re(a)), dco), # 4.9.2
|
||
|
(sinh(a*t)**2, 2*a**2/(s**3-4*a**2*s),
|
||
|
S.true, 2*Abs(re(a)), dco), # 4.9.3
|
||
|
(cosh(a*t)**2, (s**2-2*a**2)/(s**3-4*a**2*s),
|
||
|
S.true, 2*Abs(re(a)), dco), # 4.9.4
|
||
|
(sinh(a*t)/t, log((s+a)/(s-a))/2,
|
||
|
S.true, Abs(re(a)), dco), # 4.9.12
|
||
|
(t**n*sinh(a*t), gamma(n+1)/2*((s-a)**(-n-1)-(s+a)**(-n-1)),
|
||
|
n > -2, Abs(a), dco), # 4.9.18
|
||
|
(t**n*cosh(a*t), gamma(n+1)/2*((s-a)**(-n-1)+(s+a)**(-n-1)),
|
||
|
n > -1, Abs(a), dco), # 4.9.19
|
||
|
(sinh(2*sqrt(a*t)), sqrt(pi*a)/s/sqrt(s)*exp(a/s),
|
||
|
S.true, S.Zero, dco), # 4.9.34
|
||
|
(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.9.35
|
||
|
(
|
||
|
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.9.36
|
||
|
(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.9.37
|
||
|
(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.9.38
|
||
|
(cosh(2*sqrt(a*t))/sqrt(t), pi**(S(1)/2)*s**(-S(1)/2)*exp(a/s),
|
||
|
S.true, S.Zero, dco), # 4.9.39
|
||
|
(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.40
|
||
|
(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), # 4.9.41
|
||
|
(erf(a*t), exp(s**2/(2*a)**2)*erfc(s/(2*a))/s,
|
||
|
4*Abs(arg(a)) < pi, S.Zero, dco), # 4.12.2
|
||
|
(erf(sqrt(a*t)), sqrt(a)/sqrt(s+a)/s,
|
||
|
S.true, Max(S.Zero, -re(a)), dco), # 4.12.4
|
||
|
(exp(a*t)*erf(sqrt(a*t)), sqrt(a)/sqrt(s)/(s-a),
|
||
|
S.true, Max(S.Zero, re(a)), dco), # 4.12.5
|
||
|
(erf(sqrt(a/t)/2), (1-exp(-sqrt(a*s)))/s,
|
||
|
re(a) > 0, S.Zero, dco), # 4.12.6
|
||
|
(erfc(sqrt(a*t)), (sqrt(s+a)-sqrt(a))/sqrt(s+a)/s,
|
||
|
S.true, -re(a), dco), # 4.12.9
|
||
|
(exp(a*t)*erfc(sqrt(a*t)), 1/(s+sqrt(a*s)),
|
||
|
S.true, S.Zero, dco), # 4.12.10
|
||
|
(erfc(sqrt(a/t)/2), exp(-sqrt(a*s))/s,
|
||
|
re(a) > 0, S.Zero, dco), # 4.2.11
|
||
|
(besselj(n, a*t), a**n/(sqrt(s**2+a**2)*(s+sqrt(s**2+a**2))**n),
|
||
|
re(n) > -1, Abs(im(a)), dco), # 4.14.1
|
||
|
(t**b*besselj(n, a*t),
|
||
|
2**n/sqrt(pi)*gamma(n+S.Half)*a**n*(s**2+a**2)**(-n-S.Half),
|
||
|
And(re(n) > -S.Half, Eq(b, n)), Abs(im(a)), dco), # 4.14.7
|
||
|
(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(re(n) > -1, Eq(b, n+1)), Abs(im(a)), dco), # 4.14.8
|
||
|
(besselj(0, 2*sqrt(a*t)), exp(-a/s)/s,
|
||
|
S.true, S.Zero, dco), # 4.14.25
|
||
|
(t**(b)*besselj(n, 2*sqrt(a*t)), a**(n/2)*s**(-n-1)*exp(-a/s),
|
||
|
And(re(n) > -1, Eq(b, n*S.Half)), S.Zero, dco), # 4.14.30
|
||
|
(besselj(0, a*sqrt(t**2+b*t)),
|
||
|
exp(b*s-b*sqrt(s**2+a**2))/sqrt(s**2+a**2),
|
||
|
Abs(arg(b)) < pi, Abs(im(a)), dco), # 4.15.19
|
||
|
(besseli(n, a*t), a**n/(sqrt(s**2-a**2)*(s+sqrt(s**2-a**2))**n),
|
||
|
re(n) > -1, Abs(re(a)), dco), # 4.16.1
|
||
|
(t**b*besseli(n, a*t),
|
||
|
2**n/sqrt(pi)*gamma(n+S.Half)*a**n*(s**2-a**2)**(-n-S.Half),
|
||
|
And(re(n) > -S.Half, Eq(b, n)), Abs(re(a)), dco), # 4.16.6
|
||
|
(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(re(n) > -1, Eq(b, n+1)), Abs(re(a)), dco), # 4.16.7
|
||
|
(t**(b)*besseli(n, 2*sqrt(a*t)), a**(n/2)*s**(-n-1)*exp(a/s),
|
||
|
And(re(n) > -1, Eq(b, n*S.Half)), S.Zero, dco), # 4.16.18
|
||
|
(bessely(0, a*t), -2/pi*asinh(s/a)/sqrt(s**2+a**2),
|
||
|
S.true, Abs(im(a)), dco), # 4.15.44
|
||
|
(besselk(0, a*t), log((s + sqrt(s**2-a**2))/a)/(sqrt(s**2-a**2)),
|
||
|
S.true, -re(a), dco) # 4.16.23
|
||
|
]
|
||
|
return laplace_transform_rules, t, s
|
||
|
|
||
|
|
||
|
def _laplace_rule_timescale(f, t, s):
|
||
|
"""
|
||
|
This function applies the time-scaling rule of the Laplace transform in
|
||
|
a straight-forward way. For example, if it gets ``(f(a*t), t, s)``, it will
|
||
|
compute ``LaplaceTransform(f(t)/a, t, s/a)`` if ``a>0``.
|
||
|
"""
|
||
|
|
||
|
a = Wild('a', exclude=[t])
|
||
|
g = WildFunction('g', nargs=1)
|
||
|
ma1 = f.match(g)
|
||
|
if ma1:
|
||
|
arg = ma1[g].args[0].collect(t)
|
||
|
ma2 = arg.match(a*t)
|
||
|
if ma2 and ma2[a].is_positive and ma2[a] != 1:
|
||
|
debug('_laplace_apply_prog rules match:')
|
||
|
debugf(' f: %s _ %s, %s )', (f, ma1, ma2))
|
||
|
debug(' rule: time scaling (4.1.4)')
|
||
|
r, pr, cr = _laplace_transform(1/ma2[a]*ma1[g].func(t),
|
||
|
t, s/ma2[a], simplify=False)
|
||
|
return (r, pr, cr)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_rule_heaviside(f, t, s):
|
||
|
"""
|
||
|
This function deals with time-shifted Heaviside step functions. If the time
|
||
|
shift is positive, it applies the time-shift rule of the Laplace transform.
|
||
|
For example, if it gets ``(Heaviside(t-a)*f(t), t, s)``, it will compute
|
||
|
``exp(-a*s)*LaplaceTransform(f(t+a), t, s)``.
|
||
|
|
||
|
If the time shift is negative, the Heaviside function is simply removed
|
||
|
as it means nothing to the Laplace transform.
|
||
|
|
||
|
The function does not remove a factor ``Heaviside(t)``; this is done by
|
||
|
the simple rules.
|
||
|
"""
|
||
|
|
||
|
a = Wild('a', exclude=[t])
|
||
|
y = Wild('y')
|
||
|
g = Wild('g')
|
||
|
ma1 = f.match(Heaviside(y)*g)
|
||
|
if ma1:
|
||
|
ma2 = ma1[y].match(t-a)
|
||
|
if ma2 and ma2[a].is_positive:
|
||
|
debug('_laplace_apply_prog_rules match:')
|
||
|
debugf(' f: %s ( %s, %s )', (f, ma1, ma2))
|
||
|
debug(' rule: time shift (4.1.4)')
|
||
|
r, pr, cr = _laplace_transform(ma1[g].subs(t, t+ma2[a]), t, s,
|
||
|
simplify=False)
|
||
|
return (exp(-ma2[a]*s)*r, pr, cr)
|
||
|
if ma2 and ma2[a].is_negative:
|
||
|
debug('_laplace_apply_prog_rules match:')
|
||
|
debugf(' f: %s ( %s, %s )', (f, ma1, ma2))
|
||
|
debug(' rule: Heaviside factor, negative time shift (4.1.4)')
|
||
|
r, pr, cr = _laplace_transform(ma1[g], t, s, simplify=False)
|
||
|
return (r, pr, cr)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_rule_exp(f, t, s):
|
||
|
"""
|
||
|
If this function finds a factor ``exp(a*t)``, it applies the
|
||
|
frequency-shift rule of the Laplace transform and adjusts the convergence
|
||
|
plane accordingly. For example, if it gets ``(exp(-a*t)*f(t), t, s)``, it
|
||
|
will compute ``LaplaceTransform(f(t), t, s+a)``.
|
||
|
"""
|
||
|
|
||
|
a = Wild('a', exclude=[t])
|
||
|
y = Wild('y')
|
||
|
z = Wild('z')
|
||
|
ma1 = f.match(exp(y)*z)
|
||
|
if ma1:
|
||
|
ma2 = ma1[y].collect(t).match(a*t)
|
||
|
if ma2:
|
||
|
debug('_laplace_apply_prog_rules match:')
|
||
|
debugf(' f: %s ( %s, %s )', (f, ma1, ma2))
|
||
|
debug(' rule: multiply with exp (4.1.5)')
|
||
|
r, pr, cr = _laplace_transform(ma1[z], t, s-ma2[a],
|
||
|
simplify=False)
|
||
|
return (r, pr+re(ma2[a]), cr)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_rule_delta(f, t, s):
|
||
|
"""
|
||
|
If this function finds a factor ``DiracDelta(b*t-a)``, it applies the
|
||
|
masking property of the delta distribution. For example, if it gets
|
||
|
``(DiracDelta(t-a)*f(t), t, s)``, it will return
|
||
|
``(f(a)*exp(-a*s), -a, True)``.
|
||
|
"""
|
||
|
# This rule is not in Bateman54
|
||
|
|
||
|
a = Wild('a', exclude=[t])
|
||
|
b = Wild('b', exclude=[t])
|
||
|
|
||
|
y = Wild('y')
|
||
|
z = Wild('z')
|
||
|
ma1 = f.match(DiracDelta(y)*z)
|
||
|
if ma1 and not ma1[z].has(DiracDelta):
|
||
|
ma2 = ma1[y].collect(t).match(b*t-a)
|
||
|
if ma2:
|
||
|
debug('_laplace_apply_prog_rules match:')
|
||
|
debugf(' f: %s ( %s, %s )', (f, ma1, ma2))
|
||
|
debug(' rule: multiply with DiracDelta')
|
||
|
loc = ma2[a]/ma2[b]
|
||
|
if re(loc) >= 0 and im(loc) == 0:
|
||
|
r = exp(-ma2[a]/ma2[b]*s)*ma1[z].subs(t, ma2[a]/ma2[b])/ma2[b]
|
||
|
return (r, S.NegativeInfinity, S.true)
|
||
|
else:
|
||
|
return (0, S.NegativeInfinity, S.true)
|
||
|
if ma1[y].is_polynomial(t):
|
||
|
ro = roots(ma1[y], t)
|
||
|
if roots is not {} and set(ro.values()) == {1}:
|
||
|
slope = diff(ma1[y], t)
|
||
|
r = Add(
|
||
|
*[exp(-x*s)*ma1[z].subs(t, s)/slope.subs(t, x)
|
||
|
for x in list(ro.keys()) if im(x) == 0 and re(x) >= 0])
|
||
|
return (r, S.NegativeInfinity, S.true)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_trig_split(fn):
|
||
|
"""
|
||
|
Helper function for `_laplace_rule_trig`. This function returns two terms
|
||
|
`f` and `g`. `f` contains all product terms with sin, cos, sinh, cosh in
|
||
|
them; `g` contains everything else.
|
||
|
"""
|
||
|
trigs = [S.One]
|
||
|
other = [S.One]
|
||
|
for term in Mul.make_args(fn):
|
||
|
if term.has(sin, cos, sinh, cosh, exp):
|
||
|
trigs.append(term)
|
||
|
else:
|
||
|
other.append(term)
|
||
|
f = Mul(*trigs)
|
||
|
g = Mul(*other)
|
||
|
return f, g
|
||
|
|
||
|
|
||
|
def _laplace_trig_expsum(f, t):
|
||
|
"""
|
||
|
Helper function for `_laplace_rule_trig`. This function expects the `f`
|
||
|
from `_laplace_trig_split`. It returns two lists `xm` and `xn`. `xm` is
|
||
|
a list of dictionaries with keys `k` and `a` representing a function
|
||
|
`k*exp(a*t)`. `xn` is a list of all terms that cannot be brought into
|
||
|
that form, which may happen, e.g., when a trigonometric function has
|
||
|
another function in its argument.
|
||
|
"""
|
||
|
m = Wild('m')
|
||
|
p = Wild('p', exclude=[t])
|
||
|
xm = []
|
||
|
xn = []
|
||
|
|
||
|
x1 = f.rewrite(exp).expand()
|
||
|
|
||
|
for term in Add.make_args(x1):
|
||
|
if not term.has(t):
|
||
|
xm.append({'k': term, 'a': 0, re: 0, im: 0})
|
||
|
continue
|
||
|
term = term.powsimp(combine='exp')
|
||
|
if (r := term.match(p*exp(m))) is not None:
|
||
|
if (mp := r[m].as_poly(t)) is not None:
|
||
|
mc = mp.all_coeffs()
|
||
|
if len(mc) == 2:
|
||
|
xm.append({
|
||
|
'k': r[p]*exp(mc[1]), 'a': mc[0],
|
||
|
re: re(mc[0]), im: im(mc[0])})
|
||
|
else:
|
||
|
xn.append(term)
|
||
|
else:
|
||
|
xn.append(term)
|
||
|
else:
|
||
|
xn.append(term)
|
||
|
return xm, xn
|
||
|
|
||
|
|
||
|
def _laplace_trig_ltex(xm, t, s):
|
||
|
"""
|
||
|
Helper function for `_laplace_rule_trig`. This function takes the list of
|
||
|
exponentials `xm` from `_laplace_trig_expsum` and simplifies complex
|
||
|
conjugate and real symmetric poles. It returns the result as a sum and
|
||
|
the convergence plane.
|
||
|
"""
|
||
|
results = []
|
||
|
planes = []
|
||
|
|
||
|
def _simpc(coeffs):
|
||
|
nc = coeffs.copy()
|
||
|
for k in range(len(nc)):
|
||
|
ri = nc[k].as_real_imag()
|
||
|
if ri[0].has(im):
|
||
|
nc[k] = nc[k].rewrite(cos)
|
||
|
else:
|
||
|
nc[k] = (ri[0] + I*ri[1]).rewrite(cos)
|
||
|
return nc
|
||
|
|
||
|
def _quadpole(t1, k1, k2, k3, s):
|
||
|
a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im]
|
||
|
nc = [
|
||
|
k0 + k1 + k2 + k3,
|
||
|
a*(k0 + k1 - k2 - k3) - 2*I*a_i*k1 + 2*I*a_i*k2,
|
||
|
(
|
||
|
a**2*(-k0 - k1 - k2 - k3) +
|
||
|
a*(4*I*a_i*k0 + 4*I*a_i*k3) +
|
||
|
4*a_i**2*k0 + 4*a_i**2*k3),
|
||
|
(
|
||
|
a**3*(-k0 - k1 + k2 + k3) +
|
||
|
a**2*(4*I*a_i*k0 + 2*I*a_i*k1 - 2*I*a_i*k2 - 4*I*a_i*k3) +
|
||
|
a*(4*a_i**2*k0 - 4*a_i**2*k3))
|
||
|
]
|
||
|
dc = [
|
||
|
S.One, S.Zero, 2*a_i**2 - 2*a_r**2,
|
||
|
S.Zero, a_i**4 + 2*a_i**2*a_r**2 + a_r**4]
|
||
|
n = Add(
|
||
|
*[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])])
|
||
|
d = Add(
|
||
|
*[x*s**y for x, y in zip(dc, range(len(dc))[::-1])])
|
||
|
debugf(' quadpole: (%s) / (%s)', (n, d))
|
||
|
return n/d
|
||
|
|
||
|
def _ccpole(t1, k1, s):
|
||
|
a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im]
|
||
|
nc = [k0 + k1, -a*k0 - a*k1 + 2*I*a_i*k0]
|
||
|
dc = [S.One, -2*a_r, a_i**2 + a_r**2]
|
||
|
n = Add(
|
||
|
*[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])])
|
||
|
d = Add(
|
||
|
*[x*s**y for x, y in zip(dc, range(len(dc))[::-1])])
|
||
|
debugf(' ccpole: (%s) / (%s)', (n, d))
|
||
|
return n/d
|
||
|
|
||
|
def _rspole(t1, k2, s):
|
||
|
a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im]
|
||
|
nc = [k0 + k2, a*k0 - a*k2 - 2*I*a_i*k0]
|
||
|
dc = [S.One, -2*I*a_i, -a_i**2 - a_r**2]
|
||
|
n = Add(
|
||
|
*[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])])
|
||
|
d = Add(
|
||
|
*[x*s**y for x, y in zip(dc, range(len(dc))[::-1])])
|
||
|
debugf(' rspole: (%s) / (%s)', (n, d))
|
||
|
return n/d
|
||
|
|
||
|
def _sypole(t1, k3, s):
|
||
|
a, k0 = t1['a'], t1['k']
|
||
|
nc = [k0 + k3, a*(k0 - k3)]
|
||
|
dc = [S.One, S.Zero, -a**2]
|
||
|
n = Add(
|
||
|
*[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])])
|
||
|
d = Add(
|
||
|
*[x*s**y for x, y in zip(dc, range(len(dc))[::-1])])
|
||
|
debugf(' sypole: (%s) / (%s)', (n, d))
|
||
|
return n/d
|
||
|
|
||
|
def _simplepole(t1, s):
|
||
|
a, k0 = t1['a'], t1['k']
|
||
|
n = k0
|
||
|
d = s - a
|
||
|
debugf(' simplepole: (%s) / (%s)', (n, d))
|
||
|
return n/d
|
||
|
|
||
|
while len(xm) > 0:
|
||
|
t1 = xm.pop()
|
||
|
i_imagsym = None
|
||
|
i_realsym = None
|
||
|
i_pointsym = None
|
||
|
# The following code checks all remaining poles. If t1 is a pole at
|
||
|
# a+b*I, then we check for a-b*I, -a+b*I, and -a-b*I, and
|
||
|
# assign the respective indices to i_imagsym, i_realsym, i_pointsym.
|
||
|
# -a-b*I / i_pointsym only applies if both a and b are != 0.
|
||
|
for i in range(len(xm)):
|
||
|
real_eq = t1[re] == xm[i][re]
|
||
|
realsym = t1[re] == -xm[i][re]
|
||
|
imag_eq = t1[im] == xm[i][im]
|
||
|
imagsym = t1[im] == -xm[i][im]
|
||
|
if realsym and imagsym and t1[re] != 0 and t1[im] != 0:
|
||
|
i_pointsym = i
|
||
|
elif realsym and imag_eq and t1[re] != 0:
|
||
|
i_realsym = i
|
||
|
elif real_eq and imagsym and t1[im] != 0:
|
||
|
i_imagsym = i
|
||
|
|
||
|
# The next part looks for four possible pole constellations:
|
||
|
# quad: a+b*I, a-b*I, -a+b*I, -a-b*I
|
||
|
# cc: a+b*I, a-b*I (a may be zero)
|
||
|
# quad: a+b*I, -a+b*I (b may be zero)
|
||
|
# point: a+b*I, -a-b*I (a!=0 and b!=0 is needed, but that has been
|
||
|
# asserted when finding i_pointsym above.)
|
||
|
# If none apply, then t1 is a simple pole.
|
||
|
if (
|
||
|
i_imagsym is not None and i_realsym is not None
|
||
|
and i_pointsym is not None):
|
||
|
results.append(
|
||
|
_quadpole(t1,
|
||
|
xm[i_imagsym]['k'], xm[i_realsym]['k'],
|
||
|
xm[i_pointsym]['k'], s))
|
||
|
planes.append(Abs(re(t1['a'])))
|
||
|
# The three additional poles have now been used; to pop them
|
||
|
# easily we have to do it from the back.
|
||
|
indices_to_pop = [i_imagsym, i_realsym, i_pointsym]
|
||
|
indices_to_pop.sort(reverse=True)
|
||
|
for i in indices_to_pop:
|
||
|
xm.pop(i)
|
||
|
elif i_imagsym is not None:
|
||
|
results.append(_ccpole(t1, xm[i_imagsym]['k'], s))
|
||
|
planes.append(t1[re])
|
||
|
xm.pop(i_imagsym)
|
||
|
elif i_realsym is not None:
|
||
|
results.append(_rspole(t1, xm[i_realsym]['k'], s))
|
||
|
planes.append(Abs(t1[re]))
|
||
|
xm.pop(i_realsym)
|
||
|
elif i_pointsym is not None:
|
||
|
results.append(_sypole(t1, xm[i_pointsym]['k'], s))
|
||
|
planes.append(Abs(t1[re]))
|
||
|
xm.pop(i_pointsym)
|
||
|
else:
|
||
|
results.append(_simplepole(t1, s))
|
||
|
planes.append(t1[re])
|
||
|
|
||
|
return Add(*results), Max(*planes)
|
||
|
|
||
|
|
||
|
def _laplace_rule_trig(fn, t_, s, doit=True, **hints):
|
||
|
"""
|
||
|
This rule covers trigonometric factors by splitting everything into a
|
||
|
sum of exponential functions and collecting complex conjugate poles and
|
||
|
real symmetric poles.
|
||
|
"""
|
||
|
t = Dummy('t', real=True)
|
||
|
|
||
|
if not fn.has(sin, cos, sinh, cosh):
|
||
|
return None
|
||
|
|
||
|
debugf('_laplace_rule_trig: (%s, %s, %s)', (fn, t_, s))
|
||
|
|
||
|
f, g = _laplace_trig_split(fn.subs(t_, t))
|
||
|
debugf(' f = %s\n g = %s', (f, g))
|
||
|
|
||
|
xm, xn = _laplace_trig_expsum(f, t)
|
||
|
debugf(' xm = %s\n xn = %s', (xm, xn))
|
||
|
|
||
|
if len(xn) > 0:
|
||
|
# not implemented yet
|
||
|
debug(' --> xn is not empty; giving up.')
|
||
|
return None
|
||
|
|
||
|
if not g.has(t):
|
||
|
r, p = _laplace_trig_ltex(xm, t, s)
|
||
|
return g*r, p, S.true
|
||
|
else:
|
||
|
# Just transform `g` and make frequency-shifted copies
|
||
|
planes = []
|
||
|
results = []
|
||
|
G, G_plane, G_cond = _laplace_transform(g, t, s)
|
||
|
for x1 in xm:
|
||
|
results.append(x1['k']*G.subs(s, s-x1['a']))
|
||
|
planes.append(G_plane+re(x1['a']))
|
||
|
return Add(*results).subs(t, t_), Max(*planes), G_cond
|
||
|
|
||
|
|
||
|
def _laplace_rule_diff(f, t, s, doit=True, **hints):
|
||
|
"""
|
||
|
This function looks for derivatives in the time domain and replaces it
|
||
|
by factors of `s` and initial conditions in the frequency domain. For
|
||
|
example, if it gets ``(diff(f(t), t), t, s)``, it will compute
|
||
|
``s*LaplaceTransform(f(t), t, s) - f(0)``.
|
||
|
"""
|
||
|
|
||
|
a = Wild('a', exclude=[t])
|
||
|
n = Wild('n', exclude=[t])
|
||
|
g = WildFunction('g')
|
||
|
ma1 = f.match(a*Derivative(g, (t, n)))
|
||
|
if ma1 and ma1[n].is_integer:
|
||
|
m = [z.has(t) for z in ma1[g].args]
|
||
|
if sum(m) == 1:
|
||
|
debug('_laplace_apply_rules match:')
|
||
|
debugf(' f, n: %s, %s', (f, ma1[n]))
|
||
|
debug(' rule: time derivative (4.1.8)')
|
||
|
d = []
|
||
|
for k in range(ma1[n]):
|
||
|
if k == 0:
|
||
|
y = ma1[g].subs(t, 0)
|
||
|
else:
|
||
|
y = Derivative(ma1[g], (t, k)).subs(t, 0)
|
||
|
d.append(s**(ma1[n]-k-1)*y)
|
||
|
r, pr, cr = _laplace_transform(ma1[g], t, s, simplify=False)
|
||
|
return (ma1[a]*(s**ma1[n]*r - Add(*d)), pr, cr)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_rule_sdiff(f, t, s, doit=True, **hints):
|
||
|
"""
|
||
|
This function looks for multiplications with polynoimials in `t` as they
|
||
|
correspond to differentiation in the frequency domain. For example, if it
|
||
|
gets ``(t*f(t), t, s)``, it will compute
|
||
|
``-Derivative(LaplaceTransform(f(t), t, s), s)``.
|
||
|
"""
|
||
|
|
||
|
if f.is_Mul:
|
||
|
pfac = [1]
|
||
|
ofac = [1]
|
||
|
for fac in Mul.make_args(f):
|
||
|
if fac.is_polynomial(t):
|
||
|
pfac.append(fac)
|
||
|
else:
|
||
|
ofac.append(fac)
|
||
|
if len(pfac) > 1:
|
||
|
pex = prod(pfac)
|
||
|
pc = Poly(pex, t).all_coeffs()
|
||
|
N = len(pc)
|
||
|
if N > 1:
|
||
|
debug('_laplace_apply_rules match:')
|
||
|
debugf(' f, n: %s, %s', (f, pfac))
|
||
|
debug(' rule: frequency derivative (4.1.6)')
|
||
|
oex = prod(ofac)
|
||
|
r_, p_, c_ = _laplace_transform(oex, t, s, simplify=False)
|
||
|
deri = [r_]
|
||
|
d1 = False
|
||
|
try:
|
||
|
d1 = -diff(deri[-1], s)
|
||
|
except ValueError:
|
||
|
d1 = False
|
||
|
if r_.has(LaplaceTransform):
|
||
|
for k in range(N-1):
|
||
|
deri.append((-1)**(k+1)*Derivative(r_, s, k+1))
|
||
|
else:
|
||
|
if d1:
|
||
|
deri.append(d1)
|
||
|
for k in range(N-2):
|
||
|
deri.append(-diff(deri[-1], s))
|
||
|
if d1:
|
||
|
r = Add(*[pc[N-n-1]*deri[n] for n in range(N)])
|
||
|
return (r, p_, c_)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_expand(f, t, s, doit=True, **hints):
|
||
|
"""
|
||
|
This function tries to expand its argument with successively stronger
|
||
|
methods: first it will expand on the top level, then it will expand any
|
||
|
multiplications in depth, then it will try all avilable expansion methods,
|
||
|
and finally it will try to expand trigonometric functions.
|
||
|
|
||
|
If it can expand, it will then compute the Laplace transform of the
|
||
|
expanded term.
|
||
|
"""
|
||
|
|
||
|
if f.is_Add:
|
||
|
return None
|
||
|
r = expand(f, deep=False)
|
||
|
if r.is_Add:
|
||
|
return _laplace_transform(r, t, s, simplify=False)
|
||
|
r = expand_mul(f)
|
||
|
if r.is_Add:
|
||
|
return _laplace_transform(r, t, s, simplify=False)
|
||
|
r = expand(f)
|
||
|
if r.is_Add:
|
||
|
return _laplace_transform(r, t, s, simplify=False)
|
||
|
if r != f:
|
||
|
return _laplace_transform(r, t, s, simplify=False)
|
||
|
r = expand(expand_trig(f))
|
||
|
if r.is_Add:
|
||
|
return _laplace_transform(r, t, s, simplify=False)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_apply_prog_rules(f, t, s):
|
||
|
"""
|
||
|
This function applies all program rules and returns the result if one
|
||
|
of them gives a result.
|
||
|
"""
|
||
|
|
||
|
prog_rules = [_laplace_rule_heaviside, _laplace_rule_delta,
|
||
|
_laplace_rule_timescale, _laplace_rule_exp,
|
||
|
_laplace_rule_trig,
|
||
|
_laplace_rule_diff, _laplace_rule_sdiff]
|
||
|
|
||
|
for p_rule in prog_rules:
|
||
|
if (L := p_rule(f, t, s)) is not None:
|
||
|
return L
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_apply_simple_rules(f, t, s):
|
||
|
"""
|
||
|
This function applies all simple rules and returns the result if one
|
||
|
of them gives a result.
|
||
|
"""
|
||
|
simple_rules, t_, s_ = _laplace_build_rules()
|
||
|
prep_old = ''
|
||
|
prep_f = ''
|
||
|
for t_dom, s_dom, check, plane, prep in simple_rules:
|
||
|
if prep_old != prep:
|
||
|
prep_f = prep(f.subs({t: t_}))
|
||
|
prep_old = prep
|
||
|
ma = prep_f.match(t_dom)
|
||
|
if ma:
|
||
|
try:
|
||
|
c = check.xreplace(ma)
|
||
|
except TypeError:
|
||
|
# This may happen if the time function has imaginary
|
||
|
# numbers in it. Then we give up.
|
||
|
continue
|
||
|
if c == S.true:
|
||
|
debug('_laplace_apply_simple_rules match:')
|
||
|
debugf(' f: %s', (f,))
|
||
|
debugf(' rule: %s o---o %s', (t_dom, s_dom))
|
||
|
debugf(' match: %s', (ma, ))
|
||
|
return (s_dom.xreplace(ma).subs({s_: s}),
|
||
|
plane.xreplace(ma), S.true)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _laplace_transform(fn, t_, s_, simplify=True):
|
||
|
"""
|
||
|
Front-end function of the Laplace transform. It tries to apply all known
|
||
|
rules recursively, and if everything else fails, it tries to integrate.
|
||
|
"""
|
||
|
debugf('[LT _l_t] (%s, %s, %s)', (fn, t_, s_))
|
||
|
|
||
|
terms = Add.make_args(fn)
|
||
|
terms_s = []
|
||
|
planes = []
|
||
|
conditions = []
|
||
|
for ff in terms:
|
||
|
k, ft = ff.as_independent(t_, as_Add=False)
|
||
|
if (r := _laplace_apply_simple_rules(ft, t_, s_)) is not None:
|
||
|
pass
|
||
|
elif (r := _laplace_apply_prog_rules(ft, t_, s_)) is not None:
|
||
|
pass
|
||
|
elif (r := _laplace_expand(ft, t_, s_)) is not None:
|
||
|
pass
|
||
|
elif any(undef.has(t_) for undef in ft.atoms(AppliedUndef)):
|
||
|
# If there are undefined functions f(t) then integration is
|
||
|
# unlikely to do anything useful so we skip it and given an
|
||
|
# unevaluated LaplaceTransform.
|
||
|
r = (LaplaceTransform(ft, t_, s_), S.NegativeInfinity, True)
|
||
|
elif (r := _laplace_transform_integration(
|
||
|
ft, t_, s_, simplify=simplify)) is not None:
|
||
|
pass
|
||
|
else:
|
||
|
r = (LaplaceTransform(ft, t_, s_), S.NegativeInfinity, True)
|
||
|
(ri_, pi_, ci_) = r
|
||
|
terms_s.append(k*ri_)
|
||
|
planes.append(pi_)
|
||
|
conditions.append(ci_)
|
||
|
|
||
|
result = Add(*terms_s)
|
||
|
if simplify:
|
||
|
result = result.simplify(doit=False)
|
||
|
plane = Max(*planes)
|
||
|
condition = And(*conditions)
|
||
|
|
||
|
return result, plane, condition
|
||
|
|
||
|
|
||
|
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.
|
||
|
|
||
|
If this is called with ``.doit()``, it returns the Laplace transform as an
|
||
|
expression. If it is called with ``.doit(noconds=False)``, it returns a
|
||
|
tuple containing the same expression, a convergence plane, and conditions.
|
||
|
"""
|
||
|
|
||
|
_name = 'Laplace'
|
||
|
|
||
|
def _compute_transform(self, f, t, s, **hints):
|
||
|
_simplify = hints.get('simplify', False)
|
||
|
LT = _laplace_transform_integration(f, t, s, simplify=_simplify)
|
||
|
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 == S.false:
|
||
|
raise IntegralTransformError(
|
||
|
'Laplace', None, 'No combined convergence.')
|
||
|
return plane, cond
|
||
|
|
||
|
def doit(self, **hints):
|
||
|
"""
|
||
|
Try to evaluate the transform in closed form.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Standard hints are the following:
|
||
|
- ``noconds``: if True, do not return convergence conditions. The
|
||
|
default setting is `True`.
|
||
|
- ``simplify``: if True, it simplifies the final result. The
|
||
|
default setting is `False`.
|
||
|
"""
|
||
|
_noconds = hints.get('noconds', True)
|
||
|
_simplify = hints.get('simplify', False)
|
||
|
|
||
|
debugf('[LT doit] (%s, %s, %s)', (self.function,
|
||
|
self.function_variable,
|
||
|
self.transform_variable))
|
||
|
|
||
|
t_ = self.function_variable
|
||
|
s_ = self.transform_variable
|
||
|
fn = self.function
|
||
|
|
||
|
r = _laplace_transform(fn, t_, s_, simplify=_simplify)
|
||
|
|
||
|
if _noconds:
|
||
|
return r[0]
|
||
|
else:
|
||
|
return r
|
||
|
|
||
|
|
||
|
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 attempted, you can switch
|
||
|
debug information on by setting ``sympy.SYMPY_DEBUG=True``. The numbers
|
||
|
of the rules in the debug information (and the code) refer to Bateman's
|
||
|
Tables of Integral Transforms [1].
|
||
|
|
||
|
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 Laplace transform cannot be fully computed in closed form, this
|
||
|
function returns expressions containing unevaluated
|
||
|
:class:`LaplaceTransform` objects.
|
||
|
|
||
|
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, simplify=True)
|
||
|
(s/(a + s), -re(a), True)
|
||
|
|
||
|
References
|
||
|
==========
|
||
|
|
||
|
.. [1] Erdelyi, A. (ed.), Tables of Integral Transforms, Volume 1,
|
||
|
Bateman Manuscript Prooject, McGraw-Hill (1954), available:
|
||
|
https://resolver.caltech.edu/CaltechAUTHORS:20140123-101456353
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
inverse_laplace_transform, mellin_transform, fourier_transform
|
||
|
hankel_transform, inverse_hankel_transform
|
||
|
|
||
|
"""
|
||
|
|
||
|
_noconds = hints.get('noconds', False)
|
||
|
_simplify = hints.get('simplify', False)
|
||
|
|
||
|
if isinstance(f, MatrixBase) and hasattr(f, 'applyfunc'):
|
||
|
|
||
|
conds = not hints.get('noconds', False)
|
||
|
|
||
|
if conds and legacy_matrix:
|
||
|
adt = 'deprecated-laplace-transform-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=adt,
|
||
|
)
|
||
|
# 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)
|
||
|
|
||
|
LT = LaplaceTransform(f, t, s).doit(noconds=False, simplify=_simplify)
|
||
|
|
||
|
if not _noconds:
|
||
|
return LT
|
||
|
else:
|
||
|
return LT[0]
|
||
|
|
||
|
|
||
|
def _inverse_laplace_transform_integration(F, s, t_, plane, simplify=True):
|
||
|
""" The backend function for inverse Laplace transforms. """
|
||
|
from sympy.integrals.meijerint import meijerint_inversion, _get_coeff_exp
|
||
|
from sympy.integrals.transforms import inverse_mellin_transform
|
||
|
|
||
|
# There are two strategies we can try:
|
||
|
# 1) Use inverse mellin transform, 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_integration(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:
|
||
|
return None
|
||
|
if f.is_Piecewise:
|
||
|
f, cond = f.args[0]
|
||
|
if f.has(Integral):
|
||
|
return None
|
||
|
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)
|
||
|
from sympy.solvers.inequalities import _solve_inequality
|
||
|
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
|
||
|
|
||
|
|
||
|
def _complete_the_square_in_denom(f, s):
|
||
|
from sympy.simplify.radsimp import fraction
|
||
|
[n, d] = fraction(f)
|
||
|
if d.is_polynomial(s):
|
||
|
cf = d.as_poly(s).all_coeffs()
|
||
|
if len(cf) == 3:
|
||
|
a, b, c = cf
|
||
|
d = a*((s+b/(2*a))**2+c/a-(b/(2*a))**2)
|
||
|
return n/d
|
||
|
|
||
|
|
||
|
@cacheit
|
||
|
def _inverse_laplace_build_rules():
|
||
|
"""
|
||
|
This is an internal helper function that returns the table of inverse
|
||
|
Laplace transform rules in terms of the time variable `t` and the
|
||
|
frequency variable `s`. It is used by `_inverse_laplace_apply_rules`.
|
||
|
"""
|
||
|
s = Dummy('s')
|
||
|
t = Dummy('t')
|
||
|
a = Wild('a', exclude=[s])
|
||
|
b = Wild('b', exclude=[s])
|
||
|
c = Wild('c', exclude=[s])
|
||
|
|
||
|
debug('_inverse_laplace_build_rules is building rules')
|
||
|
|
||
|
def _frac(f, s):
|
||
|
try:
|
||
|
return f.factor(s)
|
||
|
except PolynomialError:
|
||
|
return f
|
||
|
|
||
|
def same(f): return f
|
||
|
# This list is sorted according to the prep function needed.
|
||
|
_ILT_rules = [
|
||
|
(a/s, a, S.true, same, 1),
|
||
|
(b*(s+a)**(-c), t**(c-1)*exp(-a*t)/gamma(c), c > 0, same, 1),
|
||
|
(1/(s**2+a**2)**2, (sin(a*t) - a*t*cos(a*t))/(2*a**3),
|
||
|
S.true, same, 1),
|
||
|
# The next two rules must be there in that order. For the second
|
||
|
# one, the condition would be a != 0 or, respectively, to take the
|
||
|
# limit a -> 0 after the transform if a == 0. It is much simpler if
|
||
|
# the case a == 0 has its own rule.
|
||
|
(1/(s**b), t**(b - 1)/gamma(b), S.true, same, 1),
|
||
|
(1/(s*(s+a)**b), lowergamma(b, a*t)/(a**b*gamma(b)),
|
||
|
S.true, same, 1)
|
||
|
]
|
||
|
return _ILT_rules, s, t
|
||
|
|
||
|
|
||
|
def _inverse_laplace_apply_simple_rules(f, s, t):
|
||
|
"""
|
||
|
Helper function for the class InverseLaplaceTransform.
|
||
|
"""
|
||
|
if f == 1:
|
||
|
debug('_inverse_laplace_apply_simple_rules match:')
|
||
|
debugf(' f: %s', (1,))
|
||
|
debugf(' rule: 1 o---o DiracDelta(%s)', (t,))
|
||
|
return DiracDelta(t), S.true
|
||
|
|
||
|
_ILT_rules, s_, t_ = _inverse_laplace_build_rules()
|
||
|
_prep = ''
|
||
|
fsubs = f.subs({s: s_})
|
||
|
|
||
|
for s_dom, t_dom, check, prep, fac in _ILT_rules:
|
||
|
if _prep != (prep, fac):
|
||
|
_F = prep(fsubs*fac)
|
||
|
_prep = (prep, fac)
|
||
|
ma = _F.match(s_dom)
|
||
|
if ma:
|
||
|
try:
|
||
|
c = check.xreplace(ma)
|
||
|
except TypeError:
|
||
|
continue
|
||
|
if c == S.true:
|
||
|
debug('_inverse_laplace_apply_simple_rules match:')
|
||
|
debugf(' f: %s', (f,))
|
||
|
debugf(' rule: %s o---o %s', (s_dom, t_dom))
|
||
|
debugf(' ma: %s', (ma,))
|
||
|
return Heaviside(t)*t_dom.xreplace(ma).subs({t_: t}), S.true
|
||
|
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _inverse_laplace_time_shift(F, s, t, plane):
|
||
|
"""
|
||
|
Helper function for the class InverseLaplaceTransform.
|
||
|
"""
|
||
|
a = Wild('a', exclude=[s])
|
||
|
g = Wild('g')
|
||
|
|
||
|
if not F.has(s):
|
||
|
return F*DiracDelta(t), S.true
|
||
|
ma1 = F.match(exp(a*s))
|
||
|
if ma1:
|
||
|
if ma1[a].is_negative:
|
||
|
debug('_inverse_laplace_time_shift match:')
|
||
|
debugf(' f: %s', (F,))
|
||
|
debug(' rule: exp(-a*s) o---o DiracDelta(t-a)')
|
||
|
debugf(' ma: %s', (ma1,))
|
||
|
return DiracDelta(t+ma1[a]), S.true
|
||
|
else:
|
||
|
debug('_inverse_laplace_time_shift match: negative time shift')
|
||
|
return InverseLaplaceTransform(F, s, t, plane), S.true
|
||
|
|
||
|
ma1 = F.match(exp(a*s)*g)
|
||
|
if ma1:
|
||
|
if ma1[a].is_negative:
|
||
|
debug('_inverse_laplace_time_shift match:')
|
||
|
debugf(' f: %s', (F,))
|
||
|
debug(' rule: exp(-a*s)*F(s) o---o Heaviside(t-a)*f(t-a)')
|
||
|
debugf(' ma: %s', (ma1,))
|
||
|
return _inverse_laplace_transform(ma1[g], s, t+ma1[a], plane)
|
||
|
else:
|
||
|
debug('_inverse_laplace_time_shift match: negative time shift')
|
||
|
return InverseLaplaceTransform(F, s, t, plane), S.true
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _inverse_laplace_time_diff(F, s, t, plane):
|
||
|
"""
|
||
|
Helper function for the class InverseLaplaceTransform.
|
||
|
"""
|
||
|
n = Wild('n', exclude=[s])
|
||
|
g = Wild('g')
|
||
|
|
||
|
ma1 = F.match(s**n*g)
|
||
|
if ma1 and ma1[n].is_integer and ma1[n].is_positive:
|
||
|
debug('_inverse_laplace_time_diff match:')
|
||
|
debugf(' f: %s', (F,))
|
||
|
debug(' rule: s**n*F(s) o---o diff(f(t), t, n)')
|
||
|
debugf(' ma: %s', (ma1,))
|
||
|
r, c = _inverse_laplace_transform(ma1[g], s, t, plane)
|
||
|
r = r.replace(Heaviside(t), 1)
|
||
|
if r.has(InverseLaplaceTransform):
|
||
|
return diff(r, t, ma1[n]), c
|
||
|
else:
|
||
|
return Heaviside(t)*diff(r, t, ma1[n]), c
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _inverse_laplace_apply_prog_rules(F, s, t, plane):
|
||
|
"""
|
||
|
Helper function for the class InverseLaplaceTransform.
|
||
|
"""
|
||
|
prog_rules = [_inverse_laplace_time_shift,
|
||
|
_inverse_laplace_time_diff]
|
||
|
|
||
|
for p_rule in prog_rules:
|
||
|
if (r := p_rule(F, s, t, plane)) is not None:
|
||
|
return r
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _inverse_laplace_expand(fn, s, t, plane):
|
||
|
"""
|
||
|
Helper function for the class InverseLaplaceTransform.
|
||
|
"""
|
||
|
if fn.is_Add:
|
||
|
return None
|
||
|
r = expand(fn, deep=False)
|
||
|
if r.is_Add:
|
||
|
return _inverse_laplace_transform(r, s, t, plane)
|
||
|
r = expand_mul(fn)
|
||
|
if r.is_Add:
|
||
|
return _inverse_laplace_transform(r, s, t, plane)
|
||
|
r = expand(fn)
|
||
|
if r.is_Add:
|
||
|
return _inverse_laplace_transform(r, s, t, plane)
|
||
|
if fn.is_rational_function(s):
|
||
|
r = fn.apart(s).doit()
|
||
|
if r.is_Add:
|
||
|
return _inverse_laplace_transform(r, s, t, plane)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _inverse_laplace_rational(fn, s, t, plane, simplify):
|
||
|
"""
|
||
|
Helper function for the class InverseLaplaceTransform.
|
||
|
"""
|
||
|
debugf('[ILT _i_l_r] (%s, %s, %s)', (fn, s, t))
|
||
|
x_ = symbols('x_')
|
||
|
f = fn.apart(s)
|
||
|
terms = Add.make_args(f)
|
||
|
terms_t = []
|
||
|
conditions = [S.true]
|
||
|
for term in terms:
|
||
|
[n, d] = term.as_numer_denom()
|
||
|
dc = d.as_poly(s).all_coeffs()
|
||
|
dc_lead = dc[0]
|
||
|
dc = [x/dc_lead for x in dc]
|
||
|
nc = [x/dc_lead for x in n.as_poly(s).all_coeffs()]
|
||
|
if len(dc) == 1:
|
||
|
r = nc[0]*DiracDelta(t)
|
||
|
terms_t.append(r)
|
||
|
elif len(dc) == 2:
|
||
|
r = nc[0]*exp(-dc[1]*t)
|
||
|
terms_t.append(Heaviside(t)*r)
|
||
|
elif len(dc) == 3:
|
||
|
a = dc[1]/2
|
||
|
b = (dc[2]-a**2).factor()
|
||
|
if len(nc) == 1:
|
||
|
nc = [S.Zero] + nc
|
||
|
l, m = tuple(nc)
|
||
|
if b == 0:
|
||
|
r = (m*t+l*(1-a*t))*exp(-a*t)
|
||
|
else:
|
||
|
hyp = False
|
||
|
if b.is_negative:
|
||
|
b = -b
|
||
|
hyp = True
|
||
|
b2 = list(roots(x_**2-b, x_).keys())[0]
|
||
|
bs = sqrt(b).simplify()
|
||
|
if hyp:
|
||
|
r = (
|
||
|
l*exp(-a*t)*cosh(b2*t) + (m-a*l) /
|
||
|
bs*exp(-a*t)*sinh(bs*t))
|
||
|
else:
|
||
|
r = l*exp(-a*t)*cos(b2*t) + (m-a*l)/bs*exp(-a*t)*sin(bs*t)
|
||
|
terms_t.append(Heaviside(t)*r)
|
||
|
else:
|
||
|
ft, cond = _inverse_laplace_transform(
|
||
|
fn, s, t, plane, simplify=True, dorational=False)
|
||
|
terms_t.append(ft)
|
||
|
conditions.append(cond)
|
||
|
|
||
|
result = Add(*terms_t)
|
||
|
if simplify:
|
||
|
result = result.simplify(doit=False)
|
||
|
debugf('[ILT _i_l_r] returns %s', (result,))
|
||
|
return result, And(*conditions)
|
||
|
|
||
|
|
||
|
def _inverse_laplace_transform(
|
||
|
fn, s_, t_, plane, simplify=True, dorational=True):
|
||
|
"""
|
||
|
Front-end function of the inverse Laplace transform. It tries to apply all
|
||
|
known rules recursively. If everything else fails, it tries to integrate.
|
||
|
"""
|
||
|
terms = Add.make_args(fn)
|
||
|
terms_t = []
|
||
|
conditions = []
|
||
|
|
||
|
debugf('[ILT _i_l_t] (%s, %s, %s)', (fn, s_, t_))
|
||
|
|
||
|
for term in terms:
|
||
|
k, f = term.as_independent(s_, as_Add=False)
|
||
|
if (
|
||
|
dorational and term.is_rational_function(s_) and
|
||
|
(
|
||
|
r := _inverse_laplace_rational(
|
||
|
f, s_, t_, plane, simplify)) is not None):
|
||
|
pass
|
||
|
elif (r := _inverse_laplace_apply_simple_rules(f, s_, t_)) is not None:
|
||
|
pass
|
||
|
elif (r := _inverse_laplace_expand(f, s_, t_, plane)) is not None:
|
||
|
pass
|
||
|
elif (
|
||
|
(r := _inverse_laplace_apply_prog_rules(f, s_, t_, plane))
|
||
|
is not None):
|
||
|
pass
|
||
|
elif any(undef.has(s_) for undef in f.atoms(AppliedUndef)):
|
||
|
# If there are undefined functions f(t) then integration is
|
||
|
# unlikely to do anything useful so we skip it and given an
|
||
|
# unevaluated LaplaceTransform.
|
||
|
r = (InverseLaplaceTransform(f, s_, t_, plane), S.true)
|
||
|
elif (
|
||
|
r := _inverse_laplace_transform_integration(
|
||
|
f, s_, t_, plane, simplify=simplify)) is not None:
|
||
|
pass
|
||
|
else:
|
||
|
r = (InverseLaplaceTransform(f, s_, t_, plane), S.true)
|
||
|
(ri_, ci_) = r
|
||
|
terms_t.append(k*ri_)
|
||
|
conditions.append(ci_)
|
||
|
|
||
|
result = Add(*terms_t)
|
||
|
if simplify:
|
||
|
result = result.simplify(doit=False)
|
||
|
condition = And(*conditions)
|
||
|
|
||
|
return result, condition
|
||
|
|
||
|
|
||
|
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_integration(
|
||
|
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 doit(self, **hints):
|
||
|
"""
|
||
|
Try to evaluate the transform in closed form.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Standard hints are the following:
|
||
|
- ``noconds``: if True, do not return convergence conditions. The
|
||
|
default setting is `True`.
|
||
|
- ``simplify``: if True, it simplifies the final result. The
|
||
|
default setting is `False`.
|
||
|
"""
|
||
|
_noconds = hints.get('noconds', True)
|
||
|
_simplify = hints.get('simplify', False)
|
||
|
|
||
|
debugf('[ILT doit] (%s, %s, %s)', (self.function,
|
||
|
self.function_variable,
|
||
|
self.transform_variable))
|
||
|
|
||
|
s_ = self.function_variable
|
||
|
t_ = self.transform_variable
|
||
|
fn = self.function
|
||
|
plane = self.fundamental_plane
|
||
|
|
||
|
r = _inverse_laplace_transform(fn, s_, t_, plane, simplify=_simplify)
|
||
|
|
||
|
if _noconds:
|
||
|
return r[0]
|
||
|
else:
|
||
|
return r
|
||
|
|
||
|
|
||
|
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
|
||
|
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)
|