Search code examples
pythonoptimizationcurve-fittinglmfit

optimization of variables to have the best fit for two curves at the same time, by using lmfit


Let's say that we have two different curves with the same variables. How can I optimize the variables to have the best fit for both curves at the same time? I can optimize each curve separately but the obtained values will not provide the best fit for the other curve necessarily.

import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from lmfit import Model, Parameters


def f(wdata, a, b, c, d):
    return (  a+b*wdata + c*d*wdata  )

def g(wdata, a, b, c, d):
    return (  a**2*(wdata)**2/a*b*wdata*(c)**2  )

wdata = (1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100)
wdata= np.asarray(wdata)

ydata1 = f(wdata, -19, -60, 9, 100)
ydata2 = g(wdata, -19, -60, 9, 100)

fmodel = Model(f)
gmodel = Model(g)

params = Parameters()
params.add('a', value=-19, max = 0, vary=False)
params.add('b', value=-60, vary=True)
params.add('c', value=9, min = 0, vary=True)
params.add('xangle', value=0.05, vary=True, min=-np.pi/2, max=np.pi/2)
params.add('d', expr='(c*a/b)*sin(xangle)')


resultf = fmodel.fit(ydata1, params, wdata=wdata, method='leastsq')
print(resultf.fit_report())
plt.plot(wdata, ydata1, 'bo')
plt.plot(wdata, resultf.init_fit, 'k--')
plt.plot(wdata, resultf.best_fit, 'r-')


resultg = gmodel.fit(ydata2, params, wdata=wdata, method='leastsq')
print(resultg.fit_report())
plt.plot(wdata, ydata2, 'bo')
plt.plot(wdata, resultg.init_fit, 'k--')
plt.plot(wdata, resultg.best_fit, 'r-')


plt.show()

Solution

  • I think what you want to do is create a model function that models the concatenatation the arrays of f and g data. Perhaps something like this:

    def f_and_g(wdata, a, b, c, d):
        fmodel = f(wdata, a, b, c, d)
        gmodel = g(wdata, a, b, c, d)
        return np.concatenate((fmodel, gmodel))
    
    # turn that into a model
    fg_model = Model(f_and_g)
    
    # same parameters as before:
    params = Parameters()
    params.add('a', value=-19, max = 0, vary=False)
    params.add('b', value=-60, vary=True)
    params.add('c', value=9, min = 0, vary=True)
    params.add('xangle', value=0.05, vary=True, min=-np.pi/2, max=np.pi/2)
    params.add('d', expr='(c*a/b)*sin(xangle)')
    
    # concatenate data to build a 1D array to be modeled:
    fdata = f(wdata, -19, -60, 9, 100) + np.normal(scale=0.1, size=len(wdata))
    gdata = g(wdata, -19, -60, 9, 100) + np.normal(scale=0.1, size=len(wdata))
    
    fg_data = np.concatenate((fdata, gdata))
    
    # now fit the concatenated data to the concatenated model  
    result = fg_model.fit(fg_data, params, wdata=wdata, method='leastsq')
    print(result.fit_report())
    
    # to plot individual results, you'll have to untangle the concatenated data:
    plt.plot(wdata, fdata, 'bo', label='f data')
    plt.plot(wdata, result.best_fit[:len(wdata)], 'b--', label='f fit')
    
    plt.plot(wdata, rgdata, 'ro', label='g data')
    plt.plot(wdata, result.best_fit[len(wdata):], 'r--', label='g fit')
    plt.legend()
    plt.show()
    

    One thing that is sort of assumed here is that the f data and g data are equally scaled and important. That is not always the case. When it is not the case, you may want to add a weighting to the Model (see lmfit docs and examples). Typically, such weighting would reflect variations in the uncertainties at each data point (ie, weight=1./stderr, where stderr is the standard error in the data). But for multi-data-set fitting, you may want to alter that so that one data set (or even one part of one data set) is emphasized more than other parts.