Passing a dictionary as fitting parameters

405 views
Skip to first unread message

jensen...@gmail.com

unread,
Sep 17, 2021, 3:15:18 PM9/17/21
to lmfit-py

Hello,

I am working using LMFIT in conjunction with an external package.
The external package calculates the needed values using a dictionary.

As a simple example of why this may be necessary: consider an n degree polynomial which can be created by the other package. It calculates the value of the polynomial based on the dictionary you pass. So you can pass a dictionary of keys/values with only certain degrees of the polynomial you wish to calculate.

I see that LMFIT can't take a dictionary as a parameter. So what can I do to get around this?
I need it to be able to take this arbitrary sized dictionary
so that the calling function inside of my model can calculate the needed values.

I attached a script that kind of lays out what I'm attempting to achieve.
Thank you,
Jensen
dictionary_as_param.py

Matt Newville

unread,
Sep 19, 2021, 10:02:51 PM9/19/21
to lmfit-py
Hi Jensen,

On Fri, Sep 17, 2021 at 2:15 PM jensen...@gmail.com <jensen...@gmail.com> wrote:

Hello,

I am working using LMFIT in conjunction with an external package.
The external package calculates the needed values using a dictionary.

As a simple example of why this may be necessary: consider an n degree polynomial which can be created by the other package. It calculates the value of the polynomial based on the dictionary you pass. So you can pass a dictionary of keys/values with only certain degrees of the polynomial you wish to calculate.

I see that LMFIT can't take a dictionary as a parameter.

Right.  A Parameter maps to a fitting variable, which is a double-precision floating-point (float64)  scalar value.  A Parameter must have a value that can be obviously mapped to a float64 scalar.  Integers are OK, strings and complex values are not, and certainly not any container object (list, tuple, dict, ndarray, set, and so on).  This is not going to change.  Parameter values are float64 scalars.  

So what can I do to get around this?

Well, a lmfit Parameters object is an (ordered, but that is now redundant) dictionary of Parameter objects, keyed by name.
So, use that.  It already really is a dictionary.

I need it to be able to take this arbitrary sized dictionary

Well, ...   Maybe not really arbitrary.  Up to 100 or so, sure. 

so that the calling function inside of my model can calculate the needed values.

A lmfit Model takes a model function provided by the user and constructs a curve-fitting minimization for that model function.  It creates named Parameter objects based on the names of the function arguments of the model function.  Model will also identify independent variables that are not variables in the fit but are expected to be passed in to help the calculation of the curve-fitting Model.   Typically (but not always), there is one of these, and by default (but can be changed) the first function argument will be expected to be "the independent variable".  Typically (so, again, not always) the independent variable will be an array of the same length as the data to be modeled.  

Arguments for the model function that have default values that are not numeric (None, bool, string, etc) are also not turned into variable Parameters. 
Other function arguments to the model function are interpreted as variables and will be made into Parameter objects (so, float64 scalar values). 
Variable Parameters must be names, so using `*args` or `**kws` in the Model function do not get mapped to Parameters.

You might be able to get away with something like (similar to what we do for PolynomialModel):

```
def poly(x, c0=0, c1=0, c2=0, c3=0, c4=0, c5=0, c6=0, c7=0):
    return np.polyval([c7, c6, c5, c4, c3, c2, c1, c0], x)

model = Model(poly)
params = model.make_params(c0=1, c1=3, c2=5)
for pname in ('c3', 'c4', c5', 'c6', 'c7'):
    params[pname].vary = False
```

With this, you would have a polynomial with 3 variables, and the other arguments having the default value of 0 that will not vary in the fit.  It is easy to go up to 7 variables (and easy to modify to go to 10, if that is what you want).

That sort of approach is necessary if you want to use Model.  If you don't use Model, but use `minimize` instead, then you write an objective function, which returns the array to be minimized (so, typically 'data-model').  In that approach, the objective function takes one Parameters object (so, "arbitrarily sized", at least in principle) as the first argument and any other arguments you might want to pass in for the calculation (say, data `x` and `y` arrays), and then your objective function handles the unpacking and using of the items in the Parameters dictionary.

Which approach you want to use might be different for your example script and the real-world use case.  The Objective function approach is more flexible, but might be a little more work to get started.

--Matt 

Thomas Bersano

unread,
Sep 20, 2021, 2:23:29 PM9/20/21
to lmfit-py
Jensen,

I did something similar in the past. I started with Matt's method, but wanted greater flexibility, so I developed a dictionary method like you are describing. It starts by first defining the function with a dictionary. The coefficients must follow a very particular format 'cN = f'. Where N is a natural number and f is the value of the coefficient for the term x^N.

*********
def polynomial(x,**coeff):
    z = np.zeros(len(x))
    for k,v in coeff.items():
        splitk = k.split('c')
        try:
            power = int(splitk[1])
        except:
            raise ValueError('Coefficient keys must be of form c0,c1,c2...')
        z += v*np.power(x,power)
    return z
*********

Next, a wrapper is needed to pass the dictionary values into an lmfit Model. Similar to before, the coefficients can have the form 'cN = f' OR the f can be replaced by a dictionary containing the parameter attributes e.g. c4 = {'value': 1, 'vary': False}. Because of how the polynomial was defined above, only the terms listed will be included in the fit. All other terms will be ignored.

*********
def PolynomialFit(x,data,**coeff):
    #Generate model:
    fitmodel = Model(polynomial, independent_vars=['x'])
    params = fitmodel.make_params()
   
    for k,v in coeff.items():
        if fittabletype(v): #if value is a float (or similar), make this the starting guess
            params.add(k, value = v)
        else: #If not, assume v is a dictionary of lmfit parameters
            params.add(k, **v)
       
    #Fit model
    result = fitmodel.fit(data, params, x = x)
    return result
*********

I attached the above example along with some test code. Hope this helps!

Best,
Thomas
polynomial lmfit dict example.py
Reply all
Reply to author
Forward
0 new messages