I have written a function to compute the Laplace transform of a function using scipy.integrate.quad
. It is not a very sophisticated function and currently performs poorly on the probability density function of an Erlang distribution.
I have included all my work below. I first compute the Laplace transform and then the inverse in order to compare it to the original p.d.f. of the Erlang. I use mpmath
for this. The mpmath.invertlaplace
is not the problem as it manages to convert the closed-form Laplace transform back to the original p.d.f. quite perfectly.
Please help me understand what the problem is with my numerical laplace transform. I get the following error but have not been able to resolve it.
IntegrationWarning: The occurrence of roundoff error is detected, which prevents the requested tolerance from being achieved. The error may be underestimated. a=0,b=np.inf,limit=limit)[0]
PLOT
CODE
import numpy as np
import matplotlib.pyplot as plt
import math as m
import mpmath as mp
import scipy.stats as st
from scipy.integrate import quad
def get_laplace(func,limit=10000):
'''
Returns laplace transfrom function
'''
def laplace(s):
'''Numerical laplace transform'''
# Seperate into real and imaginary parts
x = np.real(s)
y = np.imag(s)
def real_func(t):
return m.exp(-x*t)*m.cos(y*t)*func(t)
def imag_func(t):
return m.exp(-x*t)*m.sin(y*t)*func(t)
real_integral = quad(real_func,
a=0,b=np.inf,limit=limit)[0]
imag_intergal = quad(real_func,
a=0,b=np.inf,limit=limit)[0]
return complex(real_integral,-imag_intergal)
return laplace
def L_erlang(s,lam,k):
'''
Closed form laplace transform of Erlang or Gamma distribution.
'''
return (lam/(lam+s))**k
if __name__ == '__main__':
# Setup Erlang probability density function
k = 5
lam = 1
pdf = st.erlang(a=k,scale=1/lam).pdf
# Laplace transforms
Lnum = get_laplace(pdf) # numerical approximation
L = lambda s: L_erlang(s,lam,k) # closed form
# Use mpmath library to perform inverse laplace
# Invserse transfrom on numerical laplace function
invLnum = lambda t: mp.invertlaplace(Lnum,t,
method='dehoog',
dps=5,
degree=3)
# Invserse transfrom on closed-form laplace function
invL = lambda t: mp.invertlaplace(L,t,
method='dehoog',
dps=5,
degree=3)
# Grid to visualise
T = np.linspace(0.1,10,10)
# Get values of inverse transforms
lnum = np.array([invLnum(t) for t in T])
l = np.array([invL(t) for t in T])
# Plot
plt.plot(T,lnum,label='from numerical laplace')
plt.plot(T,l,label='from closed-form laplace')
plt.plot(T,pdf(T),label='original pdf',linestyle='--')
plt.legend(loc='best')
plt.show()
UPDATE
After two cups of VERY strong coffee, I managed to see the obvious mistake and make the code work. It quite embarrassing actually. Have a look at this line of code:
imag_intergal = quad(real_func,
a=0,b=np.inf,limit=limit)[0]
Hmmm, real_func
hey? So it should read:
imag_intergal = quad(imag_func_func,
a=0,b=np.inf,limit=limit)[0]
The one gets this lovely plot:
Conclusion
So why go through all the trouble to numerically perform the Laplace transform of something that we have a closed form solution for. That is because the interest lies somewhere else. We do not have a closed expression for the conditional future lifetime distribution which is similar to the hazard function. Lets call it h
. Then for the Erlang distribution erl = st.erlang(a=k,scale=1/lam)
that has been active for tau
units of time we have h = lambda t: erl.pdf(t+tau)/erl.sf(tau)
. This distribution can be used as a holding time in a Semi-Markov Model (SMP). To analyse the transient behaviour of the SMP one use Laplace transforms. Usually only pdfs are used but now I can use hazard functions. Its pretty cool because it means one can model transient behaviour without assuming everything is new.
Good day. I am repeating the Updates section from the original questions as this is the solution to the questions. This way, the questions can be marked as resolved.
UPDATE
After two cups of VERY strong coffee, I managed to see the obvious mistake and make the code work. It quite embarrassing actually. Have a look at this line of code:
imag_intergal = quad(real_func,
a=0,b=np.inf,limit=limit)[0]
Hmmm, real_func
hey? So it should read:
imag_intergal = quad(imag_func_func,
a=0,b=np.inf,limit=limit)[0]
The one gets this lovely plot:
Conclusion
So why go through all the trouble to numerically perform the Laplace transform of something that we have a closed form solution for. That is because the interest lies somewhere else. We do not have a closed expression for the conditional future lifetime distribution which is similar to the hazard function. Lets call it h
. Then for the Erlang distribution erl = st.erlang(a=k,scale=1/lam)
that has been active for tau
units of time we have h = lambda t: erl.pdf(t+tau)/erl.sf(tau)
. This distribution can be used as a holding time in a Semi-Markov Model (SMP). To analyse the transient behaviour of the SMP one use Laplace transforms. Usually only pdfs are used but now I can use hazard functions. Its pretty cool because it means one can model transient behaviour without assuming everything is new.