Search code examples
pythoncurve-fittingconvolutiongaussianexponential

Python code for curve fitting by convolution of a gausian and multi exponential decay


I'm developing a code for fitting a data with a model which is convolution of two functions (Gaussian with multi exponential decay exp(Ax)+exp(Bx)+...). basically the fitting with only Gaussian and/or Gaussian modified https://en.wikipedia.org/wiki/Exponentially_modified_Gaussian_distribution is working perfectly fine in Lmfit but using the builtin convolution (i.e if np.convolve of two functions is used Lmfit doesn't work.

I have tried many examples on internet, so far I realized that my functions returns inf or nan values and also data is not equally spaced for being used in convolution. I found a detour for the issue by using the mathematical expression of convolution and by using scipy.optimize.curve_fit .But it is a very clumsy and time consuming, I would like to find a way to making it more sophisticated and general by using a convolution of two functions and using lmfit where I can control the parameters a lot easier.

The data set is also included in comments as your reference.

w=0.1 # is constant
def CONVSum(x,w,*p):
    n=np.int(len(p)/3)
    A=p[:n]
    B=p[n:2*n]
    C=p[2*n:3*n]
# =======================================================================
#     below formula is derived as mathematical expression of convoluted multi exponential components with a gaussian distribution based on the instruction given in http://www.np.ph.bham.ac.uk/research_resources/programs/halflife/gauss_exp_conv.pdf
# ======================================================================
    fnct=sum(np.float64([A[i]*np.exp(-B[i]*((x-C[i])-(0.5*np.square(w)*B[i])))*(1+scipy.special.erf(((x-C[i])-(np.square(w)*B[i]))/(np.sqrt(2)*w))) for i in range(n)]))
    fnct[np.isnan(fnct)]=0
    fnct[fnct<1e-12]=0
    return fnct
N=4 #number of exponential functions to be fitted
params = np.linspace(1, 0.0001, N*3); #parameters for a multiple exponential                                                                                                                        
popt,pcov = curve_fit(CONVSum,x,y,p0=params,
    bounds=((0,0,0,0,-np.inf,-np.inf,-np.inf,-np.inf,-3,-3,-3,-3),
           (1,1,1,1, np.inf, np.inf, np.inf, np.inf, 3, 3, 3, 3)),
           maxfev = 1000000)

fitted data with curve fitt

Any help or hint regarding the fitting with convolution of Gaussian and multiple exponential decay is highly appreciated, I prefer using lmfit since I can identify parameters very nicely and also to relate them to each other.

Ideally I want to fit my data with the parameters where some of them are shared among the data sets, some are delayed (+off_set).


Solution

  • Here is the equvalent of the cure fitting code given in question. I managed to creat this by using very great instruction and infromation in here and here. But still it needs to be developed.

        # =============================================================================
        #     below formula is drived as mathematical expresion of convoluted multi exponential components with a gausian distribution based on the instruction given in http://www.np.ph.bham.ac.uk/research_resources/programs/halflife/gauss_exp_conv.pdf
        # =============================================================================
    
    def CONVSum(x,params):
        fnct=sum(
                np.float64([
                        (params['amp%s_%s'%(n,i)].value)*np.exp(-(params['dec%s_%s'%(n,i)].value)*((x-(params['cen%s_%s'%(n,i)].value))-
                         (0.5*np.square((params['sig%s_%s'%(n,i)].value))*(params['dec%s_%s'%(n,i)].value))))*
                        (1+scipy.special.erf(((x-(params['cen%s_%s'%(n,i)].value))-(np.square((params['sig%s_%s'%(n,i)].value))*
                        (params['dec%s_%s'%(n,i)].value)))/(np.sqrt(2)*(params['sig%s_%s'%(n,i)].value)))) for n in range(N) for i in wav 
                        ])
                )
        fnct=fnct/fnct.max()
        return fnct
        # =============================================================================
        #  this global fit were adapted from  https://stackoverflow.com/questions/20339234/python-and-lmfit-how-to-fit-multiple-datasets-with-shared-parameters/20341726#20341726
    
        # it is of very important thet we can identify the shared parameteres for datasets
        # =============================================================================
    
    
    def objective(params, x, data):
        """ calculate total residual for fits to several data sets"""
        ndata = data.shape[0]
        resid = 0.0*data[:]
        # make residual per data set
        resid = data- CONVSum(x,params)
        # now flatten this to a 1D array, as minimize() needs
        return resid.flatten()
    
    
    # selec datasets
    x  = df[949].index
    data =df[949].values
    
    
    # create required sets of parameters, one per data set
    N=4 #number of exponential decays
    wav=[949]  #the desired data to be fitted
    fit_params = Parameters()
    for i in wav:
            for n in range(N):
                fit_params.add( 'amp%s_%s'%(n,i), value=1, min=0.0,  max=1)
                fit_params.add( 'dec%s_%s'%(n,i), value=0.5, min=-1e10,  max=1e10)
                fit_params.add( 'cen%s_%s'%(n,i), value=0.1, min=-3.0,  max=1000)
                fit_params.add( 'sig%s_%s'%(n,i), value=0.1, min=0.05, max=0.5)
    
    # now we constrain some values to have the same value
    # for example assigning sig_2, sig_3, .. sig_5 to be equal to sig_1
    for i in wav:
            for n in (1,2,3):
                print(n,i)
                fit_params['sig%s_%s'%(n,i)].expr='sig0_949'
                fit_params['cen%s_%s'%(n,i)].expr='cen0_949'
    
    # it will run the global fit to all the data sets
    result = minimize(objective, fit_params, args=(x,data)) 
    report_fit(result.params)
    
    # plot the data sets and fits
    plt.close('all')
    plt.figure()
    for i in wav:
        y_fit = CONVSum(x,result.params)
        plt.plot(x, data, 'o-', x, y_fit, '-')
        plt.xscale('symlog') 
    plt.show()
    

    fitted data with convolution of multi exponential and gausian

    unfortunately the fitted results are not very satisfying, I am still looking for some advice to improve this.