2

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?

1
  • I think I just need a way to call get_flow without invoking the self reference, which is apparently done automatically for bound methods whether "self" is passed as an argument or not. Commented Sep 30, 2011 at 21:37

5 Answers 5

2

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])
False

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

mc.optimize([2])

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

Sign up to request clarification or add additional context in comments.

1 Comment

Excellent! I'm now running it like this: popt, pcov = spopt.curve_fit(self.get_flow, pressure_drop,flow, p0=np.array([self.exh.flow_coeff])) and it works beautifully. I agree that this is a bug because if I'm giving a value for p0, it should use that to determine the number of arguments, even if p0 is a scalar.
1

If you define get_flow inside your HeatData class you'll have to have self as first parameter : def get_flow(self, pressure_drop, coeff):

EDIT: after seeking for the definition of curve_fit, i found that the prototype is curve_fit(f, xdata, ydata, p0=None, sigma=None, **kw) so the first arg must be a callable that will be called with first argument as the independent variable : Try with a closure :

def set_flow_array(self):
        """Sets experimental flow rate through heat exchanger"""
        flow = self.flow_data.flow
        pressure_drop = self.flow_data.pressure_drop
        def get_flow((pressure_drop, coeff):
           """Sets flow based on coefficient and pressure drop.""" 
           #here you can use self.what_you_need
           # you can even call a self.get_flow(pressure_drop, coeff) method :)
           flow = coeff * pressure_drop**0.5
           return flow
        popt, pcov = spopt.curve_fit(get_flow, pressure_drop, flow)
        self.exh.flow_coeff = popt
        self.exh.flow_array = ( self.exh.flow_coeff * self.exh.pressure_drop**0.5 ) 

8 Comments

I tried that, and then the error is get_flow() takes exactly 3 arguments (4 given)
Try to add self as parameter when calling curve_fit(self.get_flow, self, ...)
Currently, I've got def get_flow(self, pressure_drop, coeff): and spopt.curve_fit(self.get_flow, pressure_drop, flow) and that's still giving the error get_flow() takes exactly 3 arguments (4 given)
What happen if you call spopt.curve_fit(self.get_flow, self, pressure_drop, flow) ?
Cool idea, but this is what it does: get_flow() takes exactly 2 arguments (16 given)
|
0

Trying dropping the "self" and making the call: spopt.curve_fit(get_flow, pressure_drop, flow)

1 Comment

If get_flow is defined as a method of HeatData, then I get the following error by dropping the self: global name 'get_flow' is not defined
0

The first argument of a class method definition should always be self. That gets passed automatically and refers to the calling class, so the method always receives one more argument than you pass when calling it.

1 Comment

What's kind of odd/cool, is that the word 'self' is merely a convention, and I could use the word 'hornswaggle' instead of self. I guess this wouldn't be very descriptive coding practice though.
0

The only pythonic way to deal with this is to let Python know get_flow is a staticmethod: a function that you put in the class because conceptually it belongs there but it doesn't need to be, and therefore doesn't need self.

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

staticmethod's can be recognized by the fact that self is not used in the function.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.