pythonsympylmfit

ValueError: The model function generated NaN values and the fit aborted! showing up when curve fitting with lmfit


I am trying to fit a custom function, which involves a set lower incomplete gamma function, to a set of data I have using lmfit. I additionally want to impose a constraint on my parameters given by the equation ((A*m**k)/k)-B=1. I keep getting an error, saying it doesn't work.

Please help I do not know what I am doing.

Here is my code including the data.

import numpy as np
import sympy as sy
import matplotlib.pyplot as plt
from lmfit import Model
from lmfit import Parameters
from sympy.parsing import sympy_parser
#%%
xdata = [0.01, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360, 390, 420, 450, 480, 510, 540, 570, 600]
ydata = [1, 1.081070134, 1.123434136, 1.163246076, 1.180650102, 1.198810838, 1.20990884, 1.218026926, 1.221569822, 1.228107416, 1.223498562, 1.232861926, 1.23392966, 1.222959988, 1.21195955, 1.195828866, 1.174534424, 1.153189058, 1.136328876, 1.121319582, 1.100934304, 1.07886202, 1.073743626, 1.053117992, 1.035234418, 1.016680182, 1.002735456, 0.993092576, 0.97627191, 0.971622838, 0.94703108]
#%%
gamma = sympy_parser.parse_expr('A*lowergamma(k,m*t)/(t**k)')
sf = sympy_parser.parse_expr('-1*B*exp(-q*t)')
model_list = sy.Array((gamma, sf))
model = sum(model_list)

model_list_func = sy.lambdify(list(model_list.free_symbols), model_list)
model_func = sy.lambdify(list(model.free_symbols), model)

lm_mod = Model(model_func)
print(f'parameter names: {lm_mod.param_names}')
print(f'independent variables: {lm_mod.independent_vars}')

params = lm_mod.make_params(B=dict(value=1,min=0,max=15), k=dict(value=7.2E-1,min=0), 
                            A=dict(value=.5,min=0,max=50), q=dict(value=1.31E-2,min=0), m=dict(value=1.31E-2,min=0))
params.add('constraint_1',expr='((A*m**k)/k)-B',min=.9,max=1.1)

res = lm_mod.fit(PLt[1], params=params, t=PLt[0])

Solution

  • sympy.lowergamma is not recognized properly in the NumPy domain used by lambdify. Check:https://github.com/sympy/sympy/issues/15134

    Try using scipy package for lowergamma function. For the contrains, since you have ((A*m**k)/k)-B=1, then you do not need parameter B:

    from lmfit import Model, Parameters, report_fit
    import numpy as np
    from scipy.special import gammainc, gamma as gamma_func
    import matplotlib.pyplot as plt
    
    # Your data
    xdata = np.array([0.01, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360, 390, 420, 450, 480, 510, 540, 570, 600])
    ydata = np.array([1, 1.081070134, 1.123434136, 1.163246076, 1.180650102, 1.198810838, 1.20990884, 1.218026926, 1.221569822, 1.228107416, 1.223498562, 1.232861926, 1.23392966, 1.222959988, 1.21195955, 1.195828866, 1.174534424, 1.153189058, 1.136328876, 1.121319582, 1.100934304, 1.07886202, 1.073743626, 1.053117992, 1.035234418, 1.016680182, 1.002735456, 0.993092576, 0.97627191, 0.971622838, 0.94703108])
    
    # Define custom gamma function
    def lowergamma(k, x):
        return gammainc(k, x) * gamma_func(k)
    
    # Model function with constrained B
    def model_func(t, A, k, m, q):
        B = (A * m**k) / k - 1
        gamma_part = A * lowergamma(k, m * t) / (t**k)
        exp_part = -B * np.exp(-q * t)
        return gamma_part + exp_part
    
    # lmfit model
    lm_mod = Model(model_func)
    
    # Parameters (no B here, it's derived)
    params = Parameters()
    params.add('A', value=1.0, min=0.01, max=10)
    params.add('m', value=0.01, min=1e-5, max=1.0)
    params.add('q', value=0.01, min=1e-5, max=1.0)
    params.add('k', value=0.72, min=0.1, max=10)
    
    # Fit the model
    result = lm_mod.fit(ydata, params, t=xdata)
    
    # Show fit report
    report_fit(result)
    
    # Plot
    plt.plot(xdata, ydata, 'o', label='Data')
    plt.plot(xdata, result.best_fit, '-', label='Best Fit')
    plt.xlabel('t')
    plt.ylabel('y')
    plt.legend()
    plt.title("Fitted Model")
    plt.grid(True)
    plt.show()