I am trying to fit an exponential decay function with convolution to a measured instrument response using python
and lmfit
.
I am new to python and I am trying to follow the code in https://groups.google.com/group/lmfit-py/attach/90f51c25ebb39a52/deconvol_exp2.py?part=0.1&authuser=0.
import numpy as np
from lmfit import Model
import matplotlib.pyplot as plt
import requests
# Load data
url = requests.get('https://groups.google.com/group/lmfit-py/attach/73a983d40ad945b1/tcspcdatashifted.csv?part=0.1&authuser=0')
x,decay1,irf=np.loadtxt(url.iter_lines(),delimiter=',',unpack=True,dtype='float')
plt.figure(1)
plt.semilogy(x,decay1,x,irf)
plt.show()
# Define weights
wWeights=1/np.sqrt(decay1+1)
# define the double exponential model
def jumpexpmodel(x,tau1,ampl1,tau2,ampl2,y0,x0,args=(irf)):
ymodel=np.zeros(x.size)
t=x
c=x0
n=len(irf)
irf_s1=np.remainder(np.remainder(t-np.floor(c)-1, n)+n,n)
irf_s11=(1-c+np.floor(c))*irf[irf_s1.astype(int)]
irf_s2=np.remainder(np.remainder(t-np.ceil(c)-1,n)+n,n)
irf_s22=(c-np.floor(c))*irf[irf_s2.astype(int)]
irf_shift=irf_s11+irf_s22
irf_reshaped_norm=irf_shift/sum(irf_shift)
ymodel = ampl1*np.exp(-(x)/tau1)
ymodel+= ampl2*np.exp(-(x)/tau2)
z=Convol(ymodel,irf_reshaped_norm)
z+=y0
return z
# convolution using fft (x and h of equal length)
def Convol(x,h):
X=np.fft.fft(x)
H=np.fft.fft(h)
xch=np.real(np.fft.ifft(X*H))
return xch
# assign the model for fitting
mod=Model(jumpexpmodel)
When defining the initial parameters for the fit, I am getting the error.
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
#initialize the parameters - showing error
pars = mod.make_params(tau1=10,ampl1=1000,tau2=10,ampl2=1000,y0=0,x0=10,args=irf)
pars['x0'].vary =True
pars['y0'].vary =True
print(pars)
# fit this model with weights, initial parameters
result = mod.fit(decay1,params=pars,weights=wWeights,method='leastsq',x=x)
# print results
print(result.fit_report())
# plot results
plt.figure(5)
plt.subplot(2,1,1)
plt.semilogy(x,decay1,'r-',x,result.best_fit,'b')
plt.subplot(2,1,2)
plt.plot(x,result.residual)
plt.show()
Based on the documentation for lmfit.model
, I suspect, this is because of how argument irf
is defined in model as args=(irf)
. I have tried to pass irf
to model
instead of params
. I have also tried to use **kwargs
.
What is the correct way to incorporate irf
into the model
for convolution and fit the data?
I believe that you want to consider irf
as an additional independent variable of the model function - a value that you pass in to the function but is not treated as a variable in the fit.
To do that, just modify the signature of your model function jumpexpmodel()
to be the simpler
def jumpexpmodel(x, tau1, ampl1, tau2, ampl2, y0, x0, irf):
the body of the function is fine (in fact the args=(irf)
would not have worked because you would have needed to unpack args
-- the signature here is really what you wanted anyway).
Then tell lmfit.Model()
that irf
is an independent variable - the default is that the first argument is the only independent variable:
mod = Model(jumpexpmodel, independent_vars=('x', 'irf'))
Then, when making the parameters, do not include irf
or args
:
pars = mod.make_params(tau1=10, ampl1=1000, tau2=10, ampl2=1000, y0=0, x0=10)
but rather now pass in irf
along with x
to mod.fit()
:
result = mod.fit(decay1, params=pars, weights=wWeights, method='leastsq', x=x, irf=irf)
The rest of your program looks fine and the resulting fit will work reasonably well, giving a report of
[[Model]]
Model(jumpexpmodel)
[[Fit Statistics]]
# fitting method = leastsq
# function evals = 138
# data points = 2797
# variables = 6
chi-square = 3795.52585
reduced chi-square = 1.35991610
Akaike info crit = 865.855713
Bayesian info crit = 901.473529
[[Variables]]
tau1: 50.4330421 +/- 0.68246203 (1.35%) (init = 10)
ampl1: 2630.30664 +/- 20.1552948 (0.77%) (init = 1000)
tau2: 225.392872 +/- 2.75674753 (1.22%) (init = 10)
ampl2: 523.257894 +/- 12.4451921 (2.38%) (init = 1000)
y0: 20.7975212 +/- 0.14165429 (0.68%) (init = 0)
x0: -9.70588133 +/- 0.12597936 (1.30%) (init = 10)
[[Correlations]] (unreported correlations are < 0.100)
C(tau2, ampl2) = -0.947
C(tau1, ampl2) = -0.805
C(tau1, tau2) = 0.706
C(tau1, x0) = -0.562
C(ampl1, x0) = 0.514
C(tau1, ampl1) = -0.453
C(tau2, y0) = -0.426
C(ampl2, y0) = 0.314
C(ampl2, x0) = 0.291
C(tau2, x0) = -0.260
C(tau1, y0) = -0.212
C(ampl1, tau2) = 0.119
and a plot like this: