0

I am setting up a constrained optimization problem in Python 3 using Scipy. The following code is a simplified version of the code where I am having trouble, but which reproduces the bug I am trying to fix.

enter image description here

For the purposes of the code snippet, one can forget everything about constrained optimization and Scipy. Just that the list P corresponds to the flattened list of xy coordinates of a polygonal chain of 5 links (shown for example in the figure above, not necessarily drawn to scale)

The list comprehension for cons_lengthis intended to create a list of functions that computes the length of the i th link in the chain i.e. a list of functions parametrized by the link number.

import numpy as np

# number of links in the polygonal chain
N = 5       

# Flattened list of xy coordinates of the vertices of a chain of N links 
# (the length of such a flattened list is 2*N+2), in this case 12. 
P = [1  ,1,  \
     2  ,3,  \
     5  ,8,  \
     13 ,21, \
     34 ,55, \
     89 ,144]

# extract the x coordinate of the ith point
def x(i): 
        return lambda P: P[2*i]

# extract the y coordinate of the ith point
def y(i): 
        return lambda P: P[2*i+1]

# get length of the ith link in the chain 
def length(i):
    def f(P):
        linkvec = np.asarray([ x(i+1)(P) - x(i)(P), \
                               y(i+1)(P) - y(i)(P)])
        result  = np.linalg.norm(linkvec)

        print("---> Calculating length of link: ", i, " :: ", result)
        return result
    return f

# A list of functions, that should ostensibly compute the length of the ith 
# link in the polygonal chain represented by `P`
cons_length = [  lambda P: length(idx)(P)  for idx in range(N)  ]

# For each function in cons_length check what it evaluates to. 
# Why is each for loop evaluating to the same result? 
for i in range(len(cons_length)):
    print("ival: ", i, "link-length: ", cons_length[i](P), "\n" )

On running the code, I get a baffling output as shown the following screenshot:

enter image description here

Why are the results of each for loop the same?! This question is in some sense a follow-up to a question along a similar theme I has asked yesterday here: How to create a set of parametrized functions in python?

But I believe the code above takes care of the binding issues mentioned there and in particular uses the suggestion in one of the comments by juanpa.arrivillaga

The correct way to do this is to write a factory function, basicalyl, you need to create an enclosing scope that where that variable isn't being changed. I prefer something like def func_maker(idx): def f(x): return x + idx; return f (of course, with correct indenation) and then use fns.append(func_maker(idx)) in a loop.

So where am I going wrong?

smilingbuddha
  • 14,334
  • 33
  • 112
  • 189
  • There is only *one single* ``idx`` variable, and it is lazily read by the ``lambda`` functions. – MisterMiyagi Jan 18 '21 at 08:06
  • You should build the list with `append` as the comment and answer say. – molbdnilo Jan 18 '21 at 08:08
  • The TLDR to the dupe is: use ``[ lambda P, idx=idx: length(idx)(P) for idx in range(N) ]`` to bind ``idx`` *on definition*, not *on call*. – MisterMiyagi Jan 18 '21 at 08:08
  • 1
    On a side-note: Please avoid using non-essential libraries such as ``colorama`` in a "[mcve]". It is very annoying having to install the library or remove it from the code just to make it runnable. – MisterMiyagi Jan 18 '21 at 08:11
  • Note that if the duplicates (including your own earlier question) do not answer your question, please clearly explain why you think your code should work even though it does not use the approaches offered in the duplicates. This might also be a suitable topic for chat. – MisterMiyagi Jan 18 '21 at 08:16
  • Ah thank you! I'll remove the colorama libraries. I used colorama, to get the green lines so that it would be easier to read the code, (since the print statements are being used in different parts of the code). I'll remove the statement from the code. Thanks. Your suggested fix works! I never ran into this late-binding problem before (never had to create a list of functions like this ) – smilingbuddha Jan 18 '21 at 08:16
  • Memo to self: using lambdas, similarly to using list comprehensions, can make debugging hard. Use boring old functions instead. – DisappointedByUnaccountableMod Jan 18 '21 at 08:17

0 Answers0