1

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()

enter image description here

# 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?

Crops
  • 5,024
  • 5
  • 38
  • 65
  • Apologies for joining your question, I am trying to do iterative reconvolution in Python for my own FLIM work and working out how to calculate the IRF shift. Can you explain to me what the c = x0 is ? – ISquared Feb 06 '22 at 16:44
  • @lsquare1 `c = x0` is the shift parameter – Crops Mar 18 '22 at 11:38

1 Answers1

1

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:

enter image description here

M Newville
  • 7,486
  • 2
  • 16
  • 29