2

I need to create a function for an arbitrary number of peaks to pass to a least square fitting routine. For each peak there is an extra term in the function, i.e.

One term with a value at 50 returns a function: f(p, x) = p[0]*50

Two terms with values at 50, 60 returns a function: f(p, x) = p[0]*50 + p[1]*60*x

Three terms with values at 50, 60, 70 returns: f(p, x) = p[0]*50 + p[1]*60*x + p[2]*70*x^2

etc.

A couple of naive attempts are shown below,

def foo(vals):
    fn = lambda p, x: 0
    i = 0
    for v in vals:
        fn = lambda p, x : fn(p, x) + p[i] * v * x**i
        i += 1
    return fn
# Causes a recursion error (I think)

Second attempt ...

def bar(vals):
    terms = []
    i = 0
    for v in vals:
        terms.append(lambda x, p: p[i] * v * x**i)
        i += 1
    def fn(x, p):
        tvals = [t(x, p) for t in terms]
        sum = 0
        for t in terms:
            sum = sum + t(x, p)
        return sum
    return fn
# Generates the wrong values

I suspect that this is a problem with referencing, i.e. Python refers to list declarations etc. but this is a little complicated to untangle - any help would be appreciated!

3
  • Can you provide a test case, showing what you're expecting to get (and what you're getting in the second case)? Commented Dec 7, 2010 at 13:17
  • Yeah it's a bit hard to guess what you want, especially since the first does just overwrite fn alot ... Commented Dec 7, 2010 at 13:22
  • Ah, It seems in the second attempt I copied the code when I was trying to debug, it shouldn't return a list, it should return the sum of that list ... Also I explained the desired outcome a little better Commented Dec 7, 2010 at 14:18

4 Answers 4

2

How about:

def foo(vals):
    def f(p,x):
        result=0
        for i,(av,ap) in enumerate(zip(vals,p)):
            result+=av*ap*(x**i)
        return result
    return f

print(foo([50])([2],3))
# f(p,x)=50*2
# 100
print(foo([50,60])([2,3],4))
# f(p,x)=50*2+60*3*x
# 820
Sign up to request clarification or add additional context in comments.

Comments

1

You can write the whole thing as a function that returns a closure:

def make_function(vals):
    def evaluate(x,p):
        return sum(p[i] * v * x**i
                   for i,v in enumerate(vals))
    return evaluate

The issue you're running into with the terms functions comes up regularly. I wrote a long explanation about this problem before, hopefully it will be helpful.

Btw, foo and bar are syntactic variables, which means they are used to explain the syntax. For implementation problems you should really use good names that mean something in the domain, that often makes the problem much easier to understand.

2 Comments

Hmm, this returns a generator not a function ... it needs to be a function (in the mathematical sense) for the fitting routine to work
@Brendan yeah i wrote what you had as 2nd attempt before. As you can see it doesnt take much to transform it to a sum.
1

Closures don't capture the values of variables in outer scopes at the time of their creation, they really capture these variables. [lambda: i for i in range(5)] gives you five functions that all return 4, because they all refer to the same i (which is 4 when iteration ends). You can hack around this by using default arguments (which are bind values at function definition time): [lambda i=i: i for i in range(5)] works as expected.

Also, use enumerate. This, together with making fn a lambda, can reduce your code to just two, imo equally readable, lines (I'm assuming the second version, the first seems broken in more ways, as indicated in comments):

def bar(vals):
    terms = [lambda x, p, i=i, v=v: p[i] * v * x**i for i, v in enumerate(vals)]
    return lambda x, p: sum(term(x, p) for term in terms)

3 Comments

Each of these terms give the right value (although it is the sum of the terms I am after - my bad, see question comments) - I can put this in a for loop to sum them up - as in the question - but is there a more concise way to do this?
@Brendan: Sure, the sum built-in.
Ah, I didn't realise there was such a thing in plain Python (I usually use Numpy) - if I could also nominate this as a second answer I would
0

A simple modification stores a reference to each fn in a default parameter

def foo(vals):
    fn = lambda p, x: 0
    i = 0
    for v in vals:
        fn = lambda p, x, f=fn, i=i: f(p, x) + p[i] * v * x**i
        i += 1
    return fn

1 Comment

@Brendan, yeah needs to be closed on i too.

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.