Search code examples
pythoncurve-fittinggaussianiminuit

python iminuit attribute error


I like to keep the syntax in its most general form, by giving iminuit its parameters using *:

import iminuit
import numpy as np

x_data = np.array([0,1,2,3,4,5,6,7,8,9])
y_data = np.array([0,1,2,3,4,5,4,3,2,1])

def fit_function(x, *p):
    return p[0]*np.exp(-((x-p[1])**2/(2*p[2]**2)))

def minimize_me(*p):
    return sum((fit_function(x, *p) - y)**2 for x, y in zip(x_data, y_data))

p=[4.5, 5, 0.4]
print(minimize_me(*p)) # works!  --> gives: 57.1645229329

m = iminuit.Minuit(minimize_me, *p)
m.migrad() # fails!

fails with error:

AttributeError: 'float' object has no attribute 'print_banner'

Any ideas what I am doing wrong? Thanks.

p.s. this exampale is based on this SO post: https://stackoverflow.com/a/22540079/5177935


Solution

  • What's the problem?

    You are calling the Minuit initialiser like this:

    p = [4.5, 5, 0.4]
    Minuit(minimize_me, *p)
    

    which is equivalent to this:

    Minuit(minimize_me, 4.5, 5, 0.4)
    

    i.e. in Python the star results in argument list unpacking, in this case passing floats as positional arguments for parameters that shouldn't be floats:

    Minuit(fcn=minimize_me, throw_nan=4.5, pedantic=5, frontend=0.4)
    

    Calling Minuit() incorrectly should fail immediately and give a good error message. It currently doesn't, because there's no input validation implemented in the initialiser. Thanks for reporting this at https://github.com/iminuit/iminuit/issues/189.

    How to do it?

    In your case you don't care about the parameter names. But Minuit needs to have a name for each parameter. This is part of the internal data structure, and used for example for reporting results of the fit.

    Here's a generic way to handle this:

    p_vals = [4.5, 5, 0.4]
    p_names = ['par_{}'.format(_) for _ in range(len(p_vals))]
    
    m = iminuit.Minuit(
        fcn=minimize_me,
        forced_parameters=p_names,
        **dict(zip(p_names, p_vals))
    )
    print(m.parameters)
    m.migrad()  # works!