Search code examples
pythonconvolutionnon-linear-regressionlmfitdeconvolution

Iterative reconvolution fitting with measured irf using python and lmfit


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?


Solution

  • 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