Chad Chad - 1 year ago 231
Python Question

Too many arguments used by python scipy.optimize.curve_fit

I'm attempting to do some curve fitting within a class instance method, and the curve_fit function is giving my class instance method too many arguments.

The code is

class HeatData(hx.HX):
"""Class for handling data from heat exchanger experiments."""

then several lines of methods that work fine, then my function is:

def get_flow(pressure_drop, coeff):
"""Sets flow based on coefficient and pressure drop."""
flow = coeff * pressure_drop**0.5
return flow

and the curve_fit function call

def set_flow_array(self):
"""Sets experimental flow rate through heat exchanger"""
flow = self.flow_data.flow
pressure_drop = self.flow_data.pressure_drop
popt, pcov = spopt.curve_fit(self.get_flow, pressure_drop, flow)
self.exh.flow_coeff = popt
self.exh.flow_array = ( self.exh.flow_coeff * self.exh.pressure_drop**0.5 )

gives the error

get_flow() takes exactly 2 arguments (3 given)

I can make it work by defining get_flow outside of the class and calling it like this:

spopt.curve_fit(get_flow, pressure_drop, flow)

but that's no good because it really needs to be a method within the class to be as versatile as I want. How can I get this work as a class instance method?

I'd also like to be able to pass self to get_flow to give it more parameters that are not fit parameters used by curve_fit. Is this possible?

Answer Source

Unlucky case, and maybe a bug in curve_fit. curve_fit uses inspect to determine the number of starting values, which gets confused or misled if there is an extra self.

So giving a starting value should avoid the problem, I thought. However, there is also an isscalar(p0) in the condition, I have no idea why, and I think it would be good to report it as a problem or bug:

if p0 is None or isscalar(p0):
        # determine number of parameters by inspecting the function
        import inspect
        args, varargs, varkw, defaults = inspect.getargspec(f)

edit: avoiding the scalar as starting value

>>> np.isscalar([2])

means that the example with only 1 parameter works if the starting value is defined as [...], e.g.similar to example below:


An example with two arguments and a given starting value avoids the inspect call, and everything is fine:

import numpy as np
from scipy.optimize import curve_fit

class MyClass(object):
    def get_flow(self, pressure_drop, coeff, coeff2):
        """Sets flow based on coefficient and pressure drop.""" 
        flow = coeff * pressure_drop**0.5 + coeff2
        return flow

    def optimize(self, start_value=None):
        coeff = 1
        pressure_drop = np.arange(20.)
        flow = coeff * pressure_drop**0.5 + np.random.randn(20)
        return curve_fit(self.get_flow, pressure_drop, flow, p0=start_value)

mc = MyClass()
print mc.optimize([2,1])

import inspect
args, varargs, varkw, defaults = inspect.getargspec(mc.get_flow)
print args, len(args)

EDIT: This bug has been fixed so bound methods can now be passed as the first argument for curve_fit, if you have a sufficiently new version of scipy.
Commit of bug fix submission on github