Search code examples
pythonnumpyscipycurve-fittinglmfit

Scipy curve_fit bounds and conditions


I am trying to use curve_fit to fit some data. it is working great, I would just like to improve the fit with additional parameters to match assumptions (such as mechanical efficiency cannot be greater than 100% etc)

y_data = [0.90 0.90 0.90 0.90 0.90 0.90 0.90 1.30 1.30 1.30 1.30 1.20 1.65 1.65 1.65 1.65 1.65 1.65 1.80 1.80 1.80 1.80 1.80 1.80 1.80 1.80 1.80 3.50 6.60 6.60 6.70 6.70 6.70 6.70 6.70 8.50 12.70] # I am aware this does not have commas
x_data = [0.38 0.38 0.38 0.38 0.38 0.38 0.38 0.38 0.38 0.38 0.38 0.46 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 0.53 1.02 1.02 1.02 1.02 1.02 1.02 1.02 1.02 1.02] # ditto


def poly2(x, a, b, c): return a*x**2+ b*x+c

def poly3(x,a,b,c,d): return a*x**3+b*x**2+c*b*x+d

pars = fit(poly2, x_data, y_data, bounds=bounds)

But I would like to additionally specify bounds to relations between parameters eg.

B**2 -4*a*c > 0 #for poly2
b**2-3*a*c=0 #for poly3

To ensure that the fit has horizontal inflection. Is there a way to achieve this? enter image description here

Edit: I found this, it may help once I investigate:How do I put a constraint on SciPy curve fit?

How would this be done using lmfit as suggested?


Solution

  • So I believe I have solved this, based on @9dogs comment using lmfit. relevant documentation here:

    https://lmfit.github.io/lmfit-py/constraints.html

    and a helpful tutorial here:

    http://blog.danallan.com/projects/2013/model/

    For my function poly3 this seams to work to enforce horizontal or positive inflection.

    from lmfit import Parameters, Model
    def poly3(x,a,b,c,d): return a*x**3+b*x**2+c*b*x+d
    
    model = Model(poly3, independent_vars=['x'], )
    params = Parameters()
    

    apologies for teh terrible maths: the cubic dicriminant is given here as https://brilliant.org/wiki/cubic-discriminant/ b**2*c**2-4*a*c**3-4*b**3*d-27*a**2*d**2+18*a*b*c*d

    params = Parameters()
    
    params..add('a', value=1, min=0, vary=True)
    params.add('b', value=1, vary=True)
    
    params.add('c', value=1, vary=True)
    params.add('d', value=1, vary=True)
    params.add('discr', value = 0, vary= False, expr='(b**2*c**2-4*a*c**3-4*b**3*d-27*a**2*d**2+18*a*b*c*d)')
    
    result = model.fit(y_data, x=x_data, params=params) # do the work
    pars = []  # list that will contain the optimized parameters for analysis
    # create a parameters list for use in the rest of code, this is a stopgap until I refactor the rest of my code
    pars.append(result.values['a'])
    pars.append(result.values['b'])
    pars.append(result.values['c'])
    pars.append(result.values['d'])
    
    ## rest of code such as plotting
    

    If there are questions I will expand the example further.